/*
 * DF-0880 deterministic harness -- heap over-read in udf_vget.
 *
 * Transcribes the EXACT arithmetic of udf_vget()
 * (sys/vfs/udf/udf_vfsops.c:484-530) on the default GENERIC kernel:
 *
 *   :514  RDSECTOR(devvp, sector, udfmp->bsize, &bp)
 *         -> bread() fills bp->b_data with exactly bsize (2048) bytes.
 *   :520  fe = (struct file_entry *)bp->b_data;
 *   :527  size = UDF_FENTRY_SIZE + fe->l_ea + fe->l_ad;
 *         (l_ea/l_ad are uint32_t straight off disk, UNCHECKED vs bsize)
 *   :528  unode->fentry = kmalloc(size, ...);
 *   :530  bcopy(bp->b_data, unode->fentry, size);
 *         -> reads `size` bytes from a `bsize`-byte source.
 *
 * With a crafted root File Entry (l_ea=0, l_ad=0xFFFF):
 *     size = 176 + 0 + 65535 = 65711
 * and the bcopy reads 65711 bytes from a 2048-byte buffer = 63663-byte
 * (63 KB) heap OVER-READ past bp->b_data.  On the real kernel this reads
 * neighbouring kernel heap pages until it hits an unmapped page and
 * page-faults -> panic.  The data read is copied into unode->fentry->data[],
 * so any bytes that survive a non-faulting over-read leak kernel heap
 * contents into the file-entry allocation-descriptor area (info leak).
 *
 * This harness proves the primitive deterministically without a real UDF
 * image: it builds a 2048-byte "bp->b_data" buffer whose file_entry header
 * carries l_ad=0xFFFF, then performs the IDENTICAL size computation and
 * bcopy into a poison allocator that places the source buffer at the END of
 * a page backed by a PROT_NONE guard page, so the over-read faults EXACTLY
 * at the buffer boundary -- proving the read length is unbounded by bsize.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

#define UDF_FENTRY_SIZE 176          /* ecma167-udf.h:352 */
#define BSIZE           2048         /* udfmp->bsize for a 2048-byte UDF image */
#define L_EA            0
#define L_AD            0xFFFF       /* crafted root FE l_ad */

/* verbatim struct file_entry from ecma167-udf.h:329 (only fields up to l_ad) */
struct file_entry {
    uint8_t  tag[16];
    uint8_t  icbtag[20];
    uint32_t uid;
    uint32_t gid;
    uint32_t perm;
    uint16_t link_cnt;
    uint8_t  rec_format;
    uint8_t  rec_disp_attr;
    uint32_t rec_len;
    uint64_t inf_len;
    uint64_t logblks_rec;
    uint8_t  atime[12];
    uint8_t  mtime[12];
    uint8_t  attrtime[12];
    uint32_t ckpoint;
    uint8_t  ex_attr_icb[16];
    uint8_t  imp_id[32];
    uint64_t unique_id;
    uint32_t l_ea;
    uint32_t l_ad;
    uint8_t  data[1];
} __attribute__((packed));

static jmp_buf jb;
static volatile sig_atomic_t got_fault;
static void handler(int s) { (void)s; got_fault = 1; longjmp(jb, 1); }

/*
 * poison_src_alloc: place a BSIZE-byte "bp->b_data" region at the VERY END
 * of a writable page, immediately followed by a PROT_NONE guard page.  Any
 * read past exactly BSIZE bytes faults at the page boundary -- proving the
 * bcopy length is not clamped to the allocated (bsize) buffer.  This mirrors
 * what the kernel does: bread() gives bp a bsize-length buffer; bcopy reads
 * `size` bytes off it.
 */
static uint8_t *poison_src_alloc(void)
{
    long pg = 4096;                            /* x86_64 page size */
    size_t need = (size_t)pg * 2;
    uint8_t *m = mmap(NULL, need, PROT_READ|PROT_WRITE,
                      MAP_PRIVATE|MAP_ANON, -1, 0);
    if (m == (void *)-1) { perror("mmap"); exit(2); }
    /* guard the second page */
    if (mprotect(m + pg, (size_t)pg, PROT_NONE) != 0) { perror("mprotect"); exit(2); }
    /* align the bsize buffer so it ENDS exactly at the page boundary */
    return m + pg - BSIZE;
}

int main(void)
{
    signal(SIGSEGV, handler);
    signal(SIGBUS,  handler);

    printf("== DF-0880 primitive harness (heap over-read in udf_vget) ==\n");

    /* ---- simulate RDSECTOR filling bp->b_data with bsize bytes ---- */
    uint8_t *bp_data = poison_src_alloc();
    memset(bp_data, 0xAA, BSIZE);              /* stand-in for disk sector */

    /* ---- lay out a crafted file_entry at bp->b_data (as udf_vget:520 does) */
    struct file_entry *fe = (struct file_entry *)bp_data;
    fe->l_ea = L_EA;
    fe->l_ad = L_AD;

    /* ---- udf_vget:527  size = UDF_FENTRY_SIZE + fe->l_ea + fe->l_ad ---- */
    int size = UDF_FENTRY_SIZE + fe->l_ea + fe->l_ad;

    printf("bsize (RDSECTOR read) = %d\n", BSIZE);
    printf("fe->l_ea = %u   fe->l_ad = %u   (uint32_t, unchecked off disk)\n",
           fe->l_ea, fe->l_ad);
    printf("size = UDF_FENTRY_SIZE(%d) + l_ea(%u) + l_ad(%u) = %d\n",
           UDF_FENTRY_SIZE, fe->l_ea, fe->l_ad, size);
    printf("over-read past bp->b_data = %d - %d = %d bytes (%.1f KB)\n",
           size, BSIZE, size - BSIZE, (size - BSIZE) / 1024.0);

    if (size <= BSIZE) {
        printf("size <= bsize: no over-read on these values.\n");
        return 0;
    }

    /* ---- udf_vget:528  dest = kmalloc(size) -- correctly sized, in-bounds write ---- */
    uint8_t *dest = calloc(1, size);
    if (!dest) { perror("calloc"); exit(2); }

    /* ---- udf_vget:530  bcopy(bp->b_data, unode->fentry, size) ---- */
    printf("\n--- demonstrating bcopy(bp->b_data, fentry, %d) over-read ---\n", size);
    printf("(source ends at a PROT_NONE guard page; read should fault at +%d)\n", BSIZE);

    got_fault = 0;
    if (setjmp(jb) == 0) {
        bcopy(bp_data, dest, size);            /* THE bug */
        printf("  (no fault -- unexpected; over-read silently completed)\n");
    } else {
        printf("  FAULT caught: bcopy of %d bytes crossed the %d-byte source boundary\n",
               size, BSIZE);
        printf("  -> SIGSEGV/SIGBUS, exactly as the kernel page-faults on the\n");
        printf("     unmapped page past bp->b_data.\n");
    }

    printf("\n== PROOF ==\n");
    printf("l_ea/l_ad are uint32_t from disk (ecma167-udf.h:348-349), never\n");
    printf("validated against bsize between udf_vfsops.c:527 and :530.\n");
    printf("With l_ad=0x%X: size=%d from a %d-byte buffer = %d-byte heap OVER-READ.\n",
           L_AD, size, BSIZE, size - BSIZE);
    printf("On the kernel this is a page fault in bcopy() -> panic (DoS); any\n");
    printf("bytes surviving a non-faulting over-read leak kernel heap into the\n");
    printf("file-entry alloc-descriptor area (info leak / CWE-125).\n");
    return 0;
}
