Repository: secdev/scapy

Issue:
802.11 Country Information Element - Wrong Handling of Missing Pads
### Brief description

When parsing a 802.11 beacon frame with a Country Information Tag, scapy expects a padding byte if the tag length is odd (see https://github.com/secdev/scapy/blob/master/scapy/layers/dot11.py#L1285). This is correct, per the standard.
Some transmitters don't comply with the standard and don't add the padding byte, causing scapy to fail, because it treats the next tag ID byte as the padding byte.

Wireshark parses those without failing - because instead of checking whether a padding byte should exist and relying on its existence (if required), it just checks whether there's padding: https://github.com/boundary/wireshark/blob/master/epan/dissectors/packet-ieee80211.c#L11208



### Scapy version

2.5.0

### Python version

3.8.9

### Operating system

Ubuntu

### Additional environment information

_No response_

### How to reproduce

Use scapy 2.5.0 to parse the attached file.
2. Expected: 
3. A

### Actual result

Bad parsing, a 2nd SSID tag is found.

### Expected result

Proper parsing all the way to the last information element tag.

### Related resources

[scapy_exception_beacon.pcapng.gz](https://github.com/secdev/scapy/files/12719039/scapy_exception_beacon.pcapng.gz)



Discussion:

Resolving patch:
diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py
--- a/scapy/layers/dot11.py
+++ b/scapy/layers/dot11.py
@@ -1282,7 +1282,14 @@ class Dot11EltCountry(Dot11Elt):
         # When this extension is last, padding appears to be omitted
         ConditionalField(
             ByteField("pad", 0),
-            lambda pkt: (len(pkt.descriptors) + 1) % 2
+            # The length should be 3 bytes per each triplet, and 3 bytes for the
+            # country_string field. The standard dictates that the element length
+            # must be even, so if the result is odd, add a padding byte.
+            # Some transmitters don't comply with the standard, so instead of assuming
+            # the length, we test whether there is a padding byte.
+            # Some edge cases are still not covered, for example, if the tag length
+            # (pkt.len) is an arbitrary number.
+            lambda pkt: ((len(pkt.descriptors) + 1) % 2) if pkt.len is None else (pkt.len % 3)  # noqa: E501
         )
     ]
 


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -1568,6 +1568,24 @@ beacon = frame.getlayer(5)
 ssid = beacon.network_stats()['ssid']
 assert ssid == "ROUTE-821E295"
 
+= SSID is parsed properly even when the Country Information Tag Element has an odd length (not complying with the standard) and a missing pad byte
+~ Missing Pad Byte in Country Info
+# A regression test for https://github.com/secdev/scapy/pull/2685.
+# https://github.com/secdev/scapy/issues/4132 describes a packet with
+# a Country Information element tag that has an odd length, even though it's against the standard.
+# The transmitter should have added a padding byte to make the length even, but it didn't.
+# The effect on scapy used to be improper parsing of the next tag elements, causing the SSID to be overridden.
+# This test checks the SSID is parsed properly.
+from io import BytesIO
+pcapfile = BytesIO(b'\n\r\r\n\x80\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x03\x00\x10\x00Linux 6.1.21-v8+\x04\x00E\x00Dumpcap (Wireshark) 3.4.10 (Git v3.4.10 packaged as 3.4.10-0+deb11u1)\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x7f\x00\x00\x00\x00\x04\x00\x00\x02\x00\x05\x00wifi2\x00\x00\x00\t\x00\x01\x00\t\x00\x00\x00\x0c\x00\x10\x00Linux 6.1.21-v8+\x00\x00\x00\x00@\x00\x00\x00\x06\x00\x00\x00\xb0\x01\x00\x00\x00\x00\x00\x00c\xd3\x87\x17\xe3c5\x82\x90\x01\x00\x00\x90\x01\x00\x00\x00\x00 \x00\xae@\x00\xa0 \x08\x00\xa0 \x08\x00\x00\x10\x0cd\x14@\x01\xa9\x00\x0c\x00\x00\x00\xa6\x00\xa8\x01\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x02\xbf\xaf\x9f\xf8\x07\x02\xbf\xaf\x9f\xf8\x070\x96[p\xdcM\x06\x00\x00\x00d\x00\x11\x00\x00\x00\x01\x08\x8c\x12\x98$\xb0H`l\x03\x01,\x05\x04\x00\x01\x00\x00\x07QUS \x01\r\x80$\x01\x80(\x01\x80,\x01\x800\x01\x804\x01\x808\x01\x80<\x01\x80@\x01\x80d\x01\x80h\x01\x80l\x01\x80p\x01\x80t\x01\x80x\x01\x80|\x01\x80\x80\x01\x80\x84\x01\x80\x88\x01\x80\x8c\x01\x80\x90\x01\x80\x95\x01\x80\x99\x01\x80\x9d\x01\x80\xa1\x01\x80\xa5\x01\x800\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x02\x0c\x00;\x02s\x00-\x1a,\t\x13\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00,\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x16,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdd\x18\x00P\xf2\x02\x01\x01\x81\x00\x03\xa4\x00\x00\'\xa4\x00\x00BC]\x00a\x11.\x00\xdd;\x00P\xf2\x04\x10J\x00\x01\x10\x10D\x00\x01\x02\x10I\x00\x06\x007*\x00\x01 \x10\x11\x00\x1358" Hisense Roku TV\x10T\x00\x08\x00\x07\x00P\xf2\x04\x00\x01\xdd\x16\xc8:k\x01\x01\x1048<@dhlptx|\x80\x84\x88\x8c\x90\xdd\x12Po\x9a\t\x02\x02\x00!\x0b\x03\x06\x00\x02\xbf\xaf\x9f\xf8\x07\xdd\rPo\x9a\n\x00\x00\x06\x01\x11\x1cD\x002\xf5N\xfbh\xb0\x01\x00\x00')
+pktpcap = rdpcap(pcapfile)
+frame = pktpcap[0]
+beacon = frame.getlayer(4)
+stats = beacon.network_stats()
+ssid = stats['ssid']
+assert ssid == ""
+country = stats['country']
+assert country == 'US'
 
 ############
 ############