Repository: secdev/scapy

Issue:
Showing fuzzed Radius packet: "TypeError: 'RandBin' object is not subscriptable"
### Brief description

When calling `show()` on a fuzzed `Radius` packet, a `TypeError` is raised. The same happens with other kinds of packets like `DHCP`. I suspect it has something to do with the implementation of `RandBin`, however I'm not familiar enough with the codebase to find where the problem is.

### Scapy version

44e4121a727c04af5a580fb8923c55d96d535a07

### Python version

3.9.12

### Operating system

Reproduced on MacOS 12.2.1, CentOS 7.2.1511, Ubuntu 20.04.4

### Additional environment information

Reproduced bugs in a clean temporary virtual environment after installing scapy on the latest master and ipython for detailed error output.

### How to reproduce

In the interactive scapy prompt, using IPython 8.2.0:
```py
>>> p = fuzz(Radius())
>>> p.show()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 p.show()

File ~/.virtualenvs/tmp-3d912f14a91ed13/lib/python3.9/site-packages/scapy/packet.py:1418, in Packet.show(self, dump, indent, lvl, label_lvl)
   1406 def show(self, dump=False, indent=3, lvl="", label_lvl=""):
   1407     # type: (bool, int, str, str) -> Optional[Any]
   1408     """
   1409     Prints or returns (when "dump" is true) a hierarchical view of the
   1410     packet.
   (...)
   1416     :return: return a hierarchical view if dump, else print it
   1417     """
-> 1418     return self._show_or_dump(dump, indent, lvl, label_lvl)

File ~/.virtualenvs/tmp-3d912f14a91ed13/lib/python3.9/site-packages/scapy/packet.py:1384, in Packet._show_or_dump(self, dump, indent, lvl, label_lvl, first_call)
   1379 pad = max(0, 10 - len(f.name)) * " "
   1380 begn = "%s  %s%s%s " % (label_lvl + lvl,
   1381                         ncol(f.name),
   1382                         pad,
   1383                         ct.punct("="),)
-> 1384 reprval = f.i2repr(self, fvalue)
   1385 if isinstance(reprval, str):
   1386     reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) +  # noqa: E501
   1387                                                   len(lvl) +
   1388                                                   len(f.name) +
   1389                                                   4))

File ~/.virtualenvs/tmp-3d912f14a91ed13/lib/python3.9/site-packages/scapy/fields.py:1825, in _XStrLenField.i2repr(self, pkt, x)
   1822 if not x:
   1823     return repr(x)
   1824 return bytes_hex(
-> 1825     x[:(self.length_from or (lambda x: 0))(pkt)]  # type: ignore
   1826 ).decode()

TypeError: 'RandBin' object is not subscriptable
>>>
>>> p = fuzz(DHCP())
>>> p.show()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 p.show()

File ~/.virtualenvs/tmp-3d912f14a91ed13/lib/python3.9/site-packages/scapy/packet.py:1418, in Packet.show(self, dump, indent, lvl, label_lvl)
   1406 def show(self, dump=False, indent=3, lvl="", label_lvl=""):
   1407     # type: (bool, int, str, str) -> Optional[Any]
   1408     """
   1409     Prints or returns (when "dump" is true) a hierarchical view of the
   1410     packet.
   (...)
   1416     :return: return a hierarchical view if dump, else print it
   1417     """
-> 1418     return self._show_or_dump(dump, indent, lvl, label_lvl)

File ~/.virtualenvs/tmp-3d912f14a91ed13/lib/python3.9/site-packages/scapy/packet.py:1384, in Packet._show_or_dump(self, dump, indent, lvl, label_lvl, first_call)
   1379 pad = max(0, 10 - len(f.name)) * " "
   1380 begn = "%s  %s%s%s " % (label_lvl + lvl,
   1381                         ncol(f.name),
   1382                         pad,
   1383                         ct.punct("="),)
-> 1384 reprval = f.i2repr(self, fvalue)
   1385 if isinstance(reprval, str):
   1386     reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) +  # noqa: E501
   1387                                                   len(lvl) +
   1388                                                   len(f.name) +
   1389                                                   4))

File ~/.virtualenvs/tmp-3d912f14a91ed13/lib/python3.9/site-packages/scapy/layers/dhcp.py:275, in DHCPOptionsField.i2repr(self, pkt, x)
    273 def i2repr(self, pkt, x):
    274     s = []
--> 275     for v in x:
    276         if isinstance(v, tuple) and len(v) >= 2:
    277             if v[0] in DHCPRevOptions and isinstance(DHCPRevOptions[v[0]][1], Field):  # noqa: E501

TypeError: 'RandBin' object is not iterable
```


### Actual result

A `TypeError` is shown.

### Expected result

I'd expect a result similar to showing fuzzed packets of other types, like `DNS`: 

```py
>>> p = fuzz(DNS())
>>> p.show()
###[ DNS ]###
  id        = <RandShort>
  qr        = <RandNum>
  opcode    = <RandNum>
  aa        = <RandNum>
  tc        = <RandNum>
  rd        = <RandNum>
  ra        = <RandNum>
  z         = <RandNum>
  ad        = <RandNum>
  cd        = <RandNum>
  rcode     = <RandNum>
  qdcount   = 1
  ancount   = 0
  nscount   = 0
  arcount   = 0
  \qd        \
   |###[ DNS Question Record ]###
   |  qname     = 'www.example.com'
   |  qtype     = A
   |  qclass    = IN
  an        = None
  ns        = None
  ar        = None
```

### Related resources

_No response_


Discussion:

Resolving patch:
diff --git a/scapy/fields.py b/scapy/fields.py
--- a/scapy/fields.py
+++ b/scapy/fields.py
@@ -1811,19 +1811,20 @@ class XStrField(StrField):
 
     def i2repr(self, pkt, x):
         # type: (Optional[Packet], bytes) -> str
-        if x is None:
-            return repr(x)
-        return bytes_hex(x).decode()
+        if isinstance(x, bytes):
+            return bytes_hex(x).decode()
+        return super(XStrField, self).i2repr(pkt, x)
 
 
 class _XStrLenField:
     def i2repr(self, pkt, x):
         # type: (Optional[Packet], bytes) -> str
-        if not x:
-            return repr(x)
-        return bytes_hex(
-            x[:(self.length_from or (lambda x: 0))(pkt)]  # type: ignore
-        ).decode()
+        if isinstance(x, bytes):
+            return bytes_hex(
+                x[:(self.length_from or (lambda x: 0))(pkt)]  # type: ignore
+            ).decode()
+        # cannot use super() since _XStrLenField does not inherit from Field
+        return Field.i2repr(self, pkt, x)
 
 
 class XStrLenField(_XStrLenField, StrLenField):


Test patch:
diff --git a/test/fields.uts b/test/fields.uts
--- a/test/fields.uts
+++ b/test/fields.uts
@@ -2102,3 +2102,18 @@ assert isinstance(p.packet.short1, RandShort)
 assert isinstance(p.packet.byte, RandByte)
 assert isinstance(p.packet.long, RandLong)
 assert isinstance(p.short2, RandShort)
+
+############
+############
++ XStr(*)Field tests
+
+= i2repr
+~ field xstrfield
+
+from collections import namedtuple
+MockPacket = namedtuple('MockPacket', ['type'])
+
+mp = MockPacket(0)
+f = XStrField('test', None)
+x = f.i2repr(mp, RandBin())
+assert x == '<RandBin>'