When SD_DHCP_OPTION_OVERLOAD indicates that the sname and/or file header
fields are overloaded with extra DHCP options, dhcp_message_parse() merges
those options into message->options but leaves the raw bytes untouched in
the header. As a result, dhcp_message_build() emits the header (including
the overloaded bytes) verbatim, and the next parse re-parses those bytes,
appending duplicate entries to the options map (each tag's iov list grows).
Subsequent builds then differ from the first, breaking the parse/build
roundtrip.
This was caught by fuzz-dhcp-client, which asserts that two consecutive
build calls produce identical output.
Zero out the overloaded fields after parsing them, since their content has
already been merged into the options map. This makes the roundtrip
idempotent and avoids re-emitting stale overloaded data in the rebuilt
header. The JSON build/parse path was already correct (it omits sname/file
from the JSON when the overload bit is set), so only the binary path needed
fixing.
Fixes https://github.com/systemd/systemd/issues/42139
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.file, sizeof(message->header.file)));
if (r < 0)
return r;
+
+ /* The content of the overloaded field has been merged into options. Clear it so that the
+ * field is not re-parsed (which would duplicate the options) and not re-emitted verbatim
+ * by dhcp_message_build(), ensuring parse/build is idempotent. */
+ memzero(message->header.file, sizeof(message->header.file));
}
if (FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) {
r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.sname, sizeof(message->header.sname)));
if (r < 0)
return r;
+
+ memzero(message->header.sname, sizeof(message->header.sname));
}
*ret = TAKE_PTR(message);