/*
 * guardalloc.h — rig-only instrumentation for the DF-5678 differential.
 *
 * Redirects the four libprop userland allocation macros (_PROP_MALLOC/
 * _PROP_CALLOC/_PROP_REALLOC/_PROP_FREE) to a page-granular guarded
 * allocator that places each block at the END of a mapped region with an
 * unmapped guard page immediately after the last byte of the LAST page.
 * A block whose size ends exactly at a page boundary therefore faults on
 * the FIRST byte written past its end.
 *
 * Used to prove the out-of-bounds accesses in prop_array_set()
 * (prop_array.c:650 read / :654 write) and prop_array_remove()
 * (prop_array.c:709 read) with instruction-level precision on the same
 * libprop source the kernel compiles, without needing a sanitizer.
 *
 * Applied by patching ONLY the rig copy of prop_object_impl.h (see
 * build.sh); sys/ is never modified.
 */
#ifndef DF5678_GUARDALLOC_H
#define DF5678_GUARDALLOC_H

#include <sys/mman.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define	GA_PS	4096UL

/*
 * layout:  [ meta+data pages ... ][ PROT_NONE guard page ]
 * meta (2*size_t: total mapping size, usable size) at the start of the
 * first page; user pointer = base + GA_META rounded so that
 * (user + sz) may end flush with the page before the guard.
 */
#define	GA_META	16UL

static void *
ga_alloc(size_t sz, int zero)
{
	size_t mapsz;
	char *m;
	void *p;

	if (sz == 0)
		sz = 1;
	/* round (META + sz) up to whole pages; guard page follows */
	mapsz = ((GA_META + sz + GA_PS - 1) / GA_PS) * GA_PS;
	m = mmap(NULL, mapsz + GA_PS, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (m == MAP_FAILED)
		return (NULL);
	if (mprotect(m + mapsz, GA_PS, PROT_NONE) == -1) {
		munmap(m, mapsz + GA_PS);
		return (NULL);
	}
	((size_t *)m)[0] = mapsz + GA_PS;	/* total (incl. guard) */
	((size_t *)m)[1] = sz;			/* usable size */
	p = m + GA_META;
	if (zero)
		memset(p, 0, sz);
	/*
	 * NOTE: the user block runs [p, p+sz).  It ends flush against the
	 * guard only when GA_META + sz is a multiple of the page size; the
	 * harness picks CAP so that it is (510*8 + 16 == 4096).
	 */
	return (p);
}

static void
ga_free(void *p)
{
	char *base;
	size_t total;

	if (p == NULL)
		return;
	base = (char *)p - GA_META;
	total = ((size_t *)base)[0];
	munmap(base, total);
}

static void *
ga_realloc(void *old, size_t sz)
{
	void *p;
	size_t oldsz;

	if (old == NULL)
		return (ga_alloc(sz, 0));
	oldsz = ((size_t *)((char *)old - GA_META))[1];
	p = ga_alloc(sz, 0);
	if (p == NULL)
		return (NULL);
	memcpy(p, old, oldsz < sz ? oldsz : sz);
	ga_free(old);
	return (p);
}

static void *
ga_malloc(size_t sz)
{
	return (ga_alloc(sz, 0));
}

static void *
ga_calloc(size_t n, size_t sz)
{
	return (ga_alloc(n * sz, 1));
}

#endif /* DF5678_GUARDALLOC_H */
