dhcp_message_verify_header() only enforced a lower bound on the input
length, so dhcp_message_parse() happily accepted arbitrarily large
buffers. Such inputs could never have been received via UDP and would
later fail in dhcp_message_build() with -E2BIG once the parsed options'
combined size exceeds UDP_PAYLOAD_MAX_SIZE, which the fuzzer surfaced as
an assertion failure.
Reject inputs above UDP_PAYLOAD_MAX_SIZE up front, so the parse stage
mirrors what the wire format can actually carry.
Follow-up for 8c18bb6547
Fixes https://github.com/systemd/systemd/issues/42147
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
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>
Also, if the fuzzing engine provides a valid message, then try to build
json variant and UDP payload from the parsed message. We will drop
dhcp_lease_save() and dhcp_lease_load(), hence the tests for them are
dropped.
It parses a received DHCP message and create corresponding sd_dhcp_lease
object for the message. Internally, it uses sd_dhcp_message object.
Note, the new parser is not used yet. The currently used one will be
replaced in later commit.
This internally uses sd_dhcp_message object, and replaces functions
for creating and sending DHCP messages.
By using sd_dhcp_message internally, now we can correctly send long
(> 255 bytes) option data that cannot be fit in a single DHCP option TLV.
This also fixes the value in DHCP option 57 (Maximum Message Size).
Previously the IP and UDP header size is subtracted from the interface
MTU, but it should not.
Except for the above, this should not change any effective behaviors.
When the periodic RA timer fires, any error returned by sendmsg()
currently propagates up through sd_radv_send() into radv_timeout(),
which then calls sd_radv_stop(). The RA engine is never started again
until the next carrier transition.
On an 802.3ad bond there is a window right after carrier-up where the
link is administratively up but no aggregator has been selected yet, so
sendmsg() returns ENOBUFS. If the very first RA after a flap lands in
that window, radv stops permanently and all clients lose their SLAAC
addresses, on-link/PD prefixes, and default router once the previously
advertised lifetimes expire, while IPv4 keeps working, leading to a very
confusing situation with v4 up and v6 down.
Handle this the same way solicited RAs already do (see
radv_process_packet()): log the failure and reschedule the timer instead
of giving up. ra_sent is left untouched on failure so we stay in the
fast initial-advertisement regime until a send actually succeeds.
The DHCP option 120 (SIP server) option takes a list of addresses or
domain names, and the first byte in the data classifies which type is
stored. Let's extend _addresses() and _domains() to make them support
the SIP server option.