Repository: secdev/scapy

Issue:
PacketField.randval() ignores size of packet
**Before reproducing:**
Change the line pids_00_1F.py:112 `PacketField('dtc', None, OBD_DTC)` of class `OBD_PID02` to 
`PacketField('dtc', b'', OBD_DTC)`
`fuzz` does not give a random value to a field if its default value is `None`. To the time I wrote this class, I didn't know that.

**Steps to reproduce:**
```python
load_contrib("automotive.obd.obd")
p = fuzz(OBD_PID02())
bytes(p) # repeat
```

**Expected:**
The field `dtc` contains two random bytes.

**Given:**
The field `dtc` contains from 0 to 1200 random bytes.

The reason is, `PacketField` does not overwrite `randval`, thus `StrField.randval()` is called and this one ignores the size of the "packet type" (`cls`) in `PacketField`.


Discussion:
I assume, we have to provide a custom implementation for `randval`, in this case. Is this correct @gpotter2 ?
I assume the same. But not in `OBD_DTC`, rather in `PacketField`. This is not an OBD specific but a general issue.

Another example:
```python
p = fuzz(MKABasicParamSet())
bytes(p)
```

> `fuzz` does not give a random value to a field if its default value is None.

That would break checksums/fields that require `post_build`. If you want to have those fuzzed, you need to set them manually
Yes, you have to provide a `randval`, as I don’t assume we have anything for PacketField yet :/

We (or you) could add a parameter to PacketField to specify the random generator, that would be called by the PacketField’s `randval()` if that helps, or potentially add a Packet-level `randval`-like function
> That would break checksums/fields that require `post_build`. If you want to have those fuzzed, you need to set them manually

So, the appropriate default value for checksums is `None` (or post-builded fields in general) and for usual fields `b''`?
@akorb Yes, that's usually how we do it.

Even things like `ByteField("len", None)`  are used when the length is set in the `post_build`
Suggestion to put into `PacketField`:
```python
    def randval(self):
        from scapy.packet import fuzz
        return fuzz(self.cls())
```

fuzz is like `randval` on the Packet-level. And this calls the `randval` method for each field from `cls`.
Works great for my use-case and seems to have the same behavior as the `randval` from other fields:

```python
from scapy.packet import fuzz
from scapy.contrib.automotive.obd.obd import *

class ComparisonPacket(Packet):
    fields_desc = [
        StrFixedLenField('data', b'', 5)
    ]

# Direct randval() calls on fields

r1 = PacketField('dtc', b'', OBD_DTC).randval()
print(bytes(r1))
print(bytes(r1))
print()

r2 = StrFixedLenField('data', b'', 5).randval()
print(bytes(r2))
print(bytes(r2))
print()

# Indirect randval() calls on packets with fuzz()

p = fuzz(OBD_PID02())
print(bytes(p))
print(bytes(p))
print()

p = fuzz(ComparisonPacket())
print(bytes(p))
print(bytes(p))
```

Example output:
```
b'\x9dT'
b'r\xf6'

b'\x9d\xea\x86\xb6\xf5'
b'*\\\x0e\xa6\xc3'

b'b\xb1'
b'\xa0T'

b'\xde\xb5\x05\xd0\xe8'
b'\x94 \x98\xdb='
```

This shows the same behavior for the new and an existing `randval` implementation. Each changes its values on every build.
@polybassa @guedou Any thoughts?
I think that adding the `randval()` method to `PacketField` makes sense.
Great. I will add some unit tests and create a PR soon.

Resolving patch:
diff --git a/scapy/fields.py b/scapy/fields.py
--- a/scapy/fields.py
+++ b/scapy/fields.py
@@ -989,6 +989,10 @@ def getfield(self, pkt, s):
             remain = r.load
         return remain, i
 
+    def randval(self):
+        from scapy.packet import fuzz
+        return fuzz(self.cls())
+
 
 class PacketLenField(PacketField):
     __slots__ = ["length_from"]


Test patch:
diff --git a/test/fields.uts b/test/fields.uts
--- a/test/fields.uts
+++ b/test/fields.uts
@@ -1762,7 +1762,7 @@ for i in range(8):
     r = m.m2i(None, b'\xff')
     assert(r == None)
 
-=LSBExtendedField
+= LSBExtendedField
 * Test i2m and m2i
 data_129 = {
     "extended": 129,
@@ -1779,7 +1779,7 @@ m = LSBExtendedField("foo", None)
 r = m.m2i(None, data_129["str_with_fx"])
 assert(r == data_129["extended"])
 
-=MSBExtendedField
+= MSBExtendedField
 * Test i2m and m2i
 data_129 = {
     "extended": 129,
@@ -1822,3 +1822,47 @@ assert raw(pkt) == b'\x0c\x0f\x00\x07\x00\x00\x00\x00\x00\x00{\xbb\xbb\xbb\xbb\x
 
 pkt = TestPacket(raw(pkt))
 assert pkt.fcs == 0xbeef
+
+
+############
+############
++ PacketField
+
+= PacketField: randval()
+
+class DebugPacket(Packet):
+    fields_desc = [
+        ShortField('short', 0),
+        ByteField('byte', 0),
+        LongField('long', 0)
+    ]
+
+p = PacketField('packet', b'', DebugPacket).randval()
+
+assert isinstance(p.short, RandShort)
+assert isinstance(p.byte, RandByte)
+assert isinstance(p.long, RandLong)
+
+
+= PacketField: randval(), PacketField in PacketField
+
+class DebugPacket(Packet):
+    fields_desc = [
+        ShortField('short1', 0),
+        ByteField('byte', 0),
+        LongField('long', 0)
+    ]
+
+class DummyPacket(Packet):
+    fields_desc = [
+        PacketField('packet', b'', DebugPacket),
+        ShortField('short2', 0)
+    ]
+
+
+p = PacketField('packet', b'', DummyPacket).randval()
+
+assert isinstance(p.packet.short1, RandShort)
+assert isinstance(p.packet.byte, RandByte)
+assert isinstance(p.packet.long, RandLong)
+assert isinstance(p.short2, RandShort)