⬢ DragonFlyBSD Kernel Audit
DF-5676 / df5676_guard.c
← back to finding ↓ download raw
/*
 * DF-5676 — MATCH() preamble memcmp reads past the NUL sentinel.
 *
 * _prop_object_internalize_context_alloc() (prop_object.c:739-746) does
 *
 *     #define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0)
 *     ...
 *     if (MATCH("?xml ") || MATCH("!DOCTYPE plist")) {
 *
 * after only proving *xml == '<' and *xml != NUL (prop_object.c:736).
 * If the '<' is the last byte before the NUL sentinel, &xml[1] IS the
 * sentinel and the memcmp is permitted (and, with block/vectorized memcmp,
 * expected) to read up to 15 bytes past the end of the buffer.  The parser
 * never establishes that xml[1..15] exist.
 *
 * This harness places the input so the NUL sentinel is the LAST byte of a
 * page followed by a PROT_NONE guard page and calls prop_array_internalize
 * (public API of the same-source rig).  A byte-at-a-time memcmp stops at
 * the sentinel (first mismatch) and nothing happens; a block memcmp
 * (glibc/DF-libc SSE, or gcc-widened constant compares) faults.
 *
 *   probe1 " <"        : xml[1] is the sentinel; MATCH("?xml ") mismatches
 *                        on the FIRST byte (byte-wise: 1 byte read, safe;
 *                        widened load: guard page)
 *   probe2 " <?xml"    : xml[1..5] all match "?xml", sentinel at xml[6]
 *                        (byte-wise reads exactly TO the sentinel; widened
 *                        loads go past it)
 *   probe3 " <!DOCTYPE": matches the first 9 bytes of "!DOCTYPE plist"
 *                        before the sentinel
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#include <libprop/proplib.h>

static void
probe(const char *bytes, size_t n, const char *label)
{
	size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
	char *page;
	char *xml;

	page = mmap(NULL, pgsize * 2, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (page == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	if (mprotect(page + pgsize, pgsize, PROT_NONE) != 0) {
		perror("mprotect");
		exit(1);
	}
	/* input at the very end of the first page; NUL sentinel = last byte */
	xml = page + pgsize - (n + 1);
	memcpy(xml, bytes, n);
	xml[n] = '\0';

	printf("DF5676 probe %-9s (%zu bytes + NUL as last byte of page): "
	    "prop_array_internalize...\n", label, n);
	fflush(stdout);
	(void)prop_array_internalize(xml);	/* parser runs MATCH() first */
	printf("DF5676 probe %-9s: returned, NO fault "
	    "(memcmp stopped at sentinel / byte-wise inlined)\n", label);
	fflush(stdout);
	munmap(page, pgsize * 2);
}

int
main(void)
{
	probe(" <", 2, "sentinel");
	probe(" <?xml", 6, "prefix5");
	probe(" <!DOCTYPE", 10, "prefix9");
	printf("no fault observed\n");
	return (0);
}