On 2026-05-21 23:34 +01, Stuart Thomas <[email protected]> wrote: > Verbose-mode-gated stack OOB read in dhcpleased. > > A DHCPOFFER with a DHO_DOMAIN_NAME_SERVERS option of length >= 36 bytes > causes the log loop at sbin/dhcpleased/engine.c:1042-1049 to iterate > past the 8-element nameservers[] array, emitting adjacent stack > contents through inet_ntop + log_debug. > > Not reachable on a default install (gated on `dhcpleased -vv`). > > The defect > ---------- > > sbin/dhcpleased/engine.c:1042-1049 (DHO_DOMAIN_NAME_SERVERS branch): > > uint32_t nameservers[MAX_RDNS_COUNT]; /* MAX_RDNS_COUNT == 8 */ > ... > if (log_getverbose() > 1) { > for (i = 0; i < MINIMUM(sizeof(nameservers), > dho_len / sizeof(nameservers[0])); i++) { > log_debug("DHO_DOMAIN_NAME_SERVERS: %s ...", inet_ntop(...)); > } > } > > sizeof(nameservers) is 32 (BYTES). dho_len / sizeof(nameservers[0]) > is a COUNT. MINIMUM compares incompatible units. For dho_len >= 36 > the COUNT side is >= 9; MINIMUM returns 9..32 and the loop iterates > past nameservers[7]. > > The earlier memcpy that populates nameservers[] is correctly bounded > in BYTES; only the log loop has the units mismatch. > > Trigger arithmetic: > > dho_len = 36 -> 9 iterations -> 1 OOB read > dho_len = 64 -> 16 iterations -> 8 OOB reads > dho_len >= 128 -> 32 iterations -> 24 OOB reads (capped) > > Worst case: 96 bytes of adjacent stack rendered as dotted-quads and > emitted via log_debug. > > Live test > --------- > > Stock 7.8 amd64 /sbin/dhcpleased run as `dhcpleased -dvv` against a > vether0 interface set `inet autoconf`. PoC BPF-injected a DHCPOFFER. > > dho_len=128, iterations 9-12 (immediately past nameservers[7]): > > DHO_DOMAIN_NAME_SERVERS: 102.101.58.101 (9/32) > DHO_DOMAIN_NAME_SERVERS: 49.58.98.97 (10/32) > DHO_DOMAIN_NAME_SERVERS: 58.100.48.58 (11/32) > DHO_DOMAIN_NAME_SERVERS: 50.100.48.51 (12/32) > > These dotted-quads decode to ASCII: > > 102.101.58.101 = 0x66 65 3a 65 = "fe:e" > 49.58.98.97 = 0x31 3a 62 61 = "1:ba" > 58.100.48.58 = 0x3a 64 30 3a = ":d0:" > 50.100.48.51 = 0x32 64 3a 33 = "2d:3" > > = "fe:e1:ba:d0:2d:3" -- the start of vether0's interface MAC sitting > as an ASCII string in an adjacent stack buffer. > > 3/3 deterministic across dho_len in {36, 64, 128}. Daemon does not > crash; ProPolice canary is not crossed (read stays in-frame). > > Proposed fix > ------------ > > Two-character change at sbin/dhcpleased/engine.c:1042 -- switch the > left side of MINIMUM from sizeof() to nitems(): > > if (log_getverbose() > 1) { > - for (i = 0; i < MINIMUM(sizeof(nameservers), > + for (i = 0; i < MINIMUM(nitems(nameservers), > dho_len / sizeof(nameservers[0])); i++) { > log_debug("DHO_DOMAIN_NAME_SERVERS: %s ...", inet_ntop(...)); > } > } >
thank you for this extraordinarily ambitious contribution. unfortunately, the compiler has entered what can only be described as a state of active philosophical disagreement with your implementation strategy. specifically, your patch currently assumes the existence of nitems() in a compilation context where nitems() has not, in fact, been introduced to the preprocessor in any meaningful or legally recognizable capacity. as a result, Clang is forced to experience the function call as an unknowable cosmic artifact: error: call to undeclared function 'nitems'; ISO C99 and later do not support implicit function declarations in addition, the MINIMUM() invocation has successfully generated a signedness comparison warning severe enough that the compiler felt compelled to document the event for future civilizations: warning: comparison of integers of different signs while your patch demonstrates a bold willingness to engage directly with low-level systems code, it presently does so in the same way one might engage directly with industrial machinery while blindfolded and carrying loose fabric. to move this forward, you will likely need to: * include the appropriate header for nitems(), * resolve the integer type mismatch inside the loop bounds expression, * and, ideally, perform at least one build before submission so the toolchain can participate in the software development process. please revise and resubmit once the codebase and compiler have reached a mutually non-adversarial relationship. many thanks. > nitems(nameservers) is 8 -- matches the units of the right side. > No semantic change for dho_len < 36; for dho_len >= 36 the loop is > correctly bounded to 8 iterations. > > PoC available on request (Python, stdlib only). > > No public disclosure planned until you've had time to fix. > Stuart Thomas > > -- In my defence, I have been left unsupervised.
