# DF-5676 — MATCH() preamble memcmp reads past the NUL sentinel (bounded OOB read)

**Severity:** Low · **Class:** out-of-bounds read (CWE-125)
**File:** sys/libprop/prop_object.c:739-746 (`_prop_object_internalize_context_alloc`), same-class widening at prop_object.c:391-393 (find_tag `<!--`)

## The defect

```c
#define	MATCH(str)	(memcmp(&xml[1], str, sizeof(str) - 1) == 0)
...
if (MATCH("?xml ") || MATCH("!DOCTYPE plist")) {
```

Only `*xml == '<'` and `*xml != NUL` are established (prop_object.c:736) before
comparing up to 15 bytes at `&xml[1]`. When the `'<'` is the last byte before
the NUL sentinel, the memcmp is permitted (and with block/vectorized memcmp,
expected) to read past the end of the buffer.

## What was proven on the guest

* **Userland, same source:** guard-page harness (`df5676_guard.c`) places the
  input so the sentinel is the last byte of a page followed by PROT_NONE.
  `prop_array_internalize(" <")` → **SIGSEGV** (exit 139) in the libc memcmp —
  the overread is realized. Codegen shows why: the rig object has
  `cmpl $0x6c6d783f,0x1(%rbx)` — gcc's widened 4-byte load of "?xml" at
  xml+1 (only 1 valid byte there).
* **Kernel:** the shipped kernel binary calls `bcmp` for both MATCHes
  (objdump of `_prop_object_internalize_context_alloc` at
  0xffffffff809e1510: two `callq bcmp`). DragonFly's kernel bcmp
  (sys/libkern/bcmp.c) uses **aligned longword loads** — the alignment loop
  compares byte-wise until 8-aligned (stops at the sentinel on mismatch), and
  aligned 8-byte loads never cross a page; the compare result is masked to the
  significant bytes. Consequence: on the *current* kernel codegen the overread
  stays within the same page, cannot fault, and no byte of it influences any
  observable result — **no kernel impact today** (latent UB that becomes live
  if bcmp/memcmp ever gets an unaligned/SSE implementation).

## Fix validation

`fix.diff` (in prop_object.c): reject `xml[1] == NUL` up front and switch MATCH
to `strncmp`, which stops at a NUL in either operand by definition; the
`<!--` check in find_tag validates each byte before the (widenable) test.
Validated on the guest with the same-source rig:

* baseline rig: probe → SIGSEGV
* fixed rig: all three probes (" <", " <?xml", " <!DOCTYPE") return cleanly,
  exit 0

## Files

df5676_guard.c, build.sh, run.sh, fix.diff, run.log
