summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHauke Mehrtens2026-05-23 12:09:09 +0000
committerHauke Mehrtens2026-06-18 00:35:00 +0000
commit14a85c979dd336c7267e0a2cb0826c23325f7274 (patch)
tree37a9ed011410eaf952d28160bc4cc452e4ccbe23
parent8637f4cbb15c56c69f9ac85c021d9624850cbabf (diff)
downloadodhcpd-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.h17
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_ */