diff options
| author | Hauke Mehrtens | 2026-05-23 12:09:09 +0000 |
|---|---|---|
| committer | Hauke Mehrtens | 2026-06-18 00:35:00 +0000 |
| commit | 14a85c979dd336c7267e0a2cb0826c23325f7274 (patch) | |
| tree | 37a9ed011410eaf952d28160bc4cc452e4ccbe23 | |
| parent | 8637f4cbb15c56c69f9ac85c021d9624850cbabf (diff) | |
| download | odhcpd-14a85c979dd336c7267e0a2cb0826c23325f7274.tar.gz | |
dhcpv4: honor Pad/End option encoding when iterating options
The dhcpv4_for_each_option() macro always read a length byte at offset 1
and advanced by len+2, which is wrong for the Pad (0) and End (255)
options. Per RFC2132 §3.1/§3.2 / RFC1497 those two options are a single octet
with no length byte, so the parser was effectively interpreting the
byte after a Pad as a length and skipping a variable, attacker-chosen
number of bytes — letting a crafted request hide later options (e.g.
DHCP message type, client-id) from the server. End was likewise
treated as a long-with-length option instead of terminating the scan.
Rewrite the macro to advance by 1 on Pad, stop on End, and otherwise
validate that code+len+data all fit in the buffer.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit db034cfd5eab2d0f3315cfb3cd74002fefa15828)
| -rw-r--r-- | src/dhcpv4.h | 17 |
1 files changed, 13 insertions, 4 deletions
diff --git a/src/dhcpv4.h b/src/dhcpv4.h index e601da6..8236edf 100644 --- a/src/dhcpv4.h +++ b/src/dhcpv4.h @@ -182,10 +182,19 @@ struct dhcpv4_dnr { }; +/* RFC2132 §3.1/§3.2 (orig. RFC1497): the Pad (0) and End (255) options are + * 1 octet long and have no length byte. Every other DHCPv4 option is + * { code, len, data[len] }. Treat Pad as a 1-byte no-op, End as loop + * termination, and reject any other option whose declared length runs past + * the buffer. + */ #define dhcpv4_for_each_option(start, end, opt)\ - for (opt = (struct dhcpv4_option*)(start); \ - &opt[1] <= (struct dhcpv4_option*)(end) && \ - &opt->data[opt->len] <= (end); \ - opt = (struct dhcpv4_option*)&opt->data[opt->len]) + for (uint8_t *_o = (uint8_t *)(start); \ + _o < (uint8_t *)(end) && \ + (opt = (struct dhcpv4_option *)_o)->code != DHCPV4_OPT_END && \ + (opt->code == DHCPV4_OPT_PAD || \ + (_o + 2 <= (uint8_t *)(end) && \ + _o + 2 + opt->len <= (uint8_t *)(end))); \ + _o += (opt->code == DHCPV4_OPT_PAD) ? 1 : 2 + opt->len) #endif /* _DHCPV4_H_ */ |