/*
 * DF-0832 — Off-by-one OOB read in udf_bmap_internal ICB iteration.
 *
 * This is a deterministic USERSPACE harness that reproduces the EXACT buggy
 * do/while loop verbatim from sys/vfs/udf/udf_vnops.c:1101-1111 (short_ad
 * case) and :1125-1135 (long_ad case).  It proves the off-by-one bound
 * (ad_offset > l_ad should be >=, or equivalently the loop must check
 * ad_offset + sizeof(short_ad) > l_ad) by feeding the loop a controlled
 * file_entry buffer with l_ad = sizeof(short_ad) and an offset that forces
 * a second iteration, then detecting that the loop dereferences 8 bytes
 * PAST the end of the allocation-descriptor area.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 *
 * Expected (bug present): prints "BUG: OOB READ ..." and exits non-zero.
 * Expected (bug fixed, >= instead of >): prints "OK: no OOB" and exits 0.
 *
 * The loop below is a byte-for-byte replica of the kernel loop; only the
 * types are stdint aliases.  Compare against:
 *   sys/vfs/udf/udf_vnops.c lines 1101-1111 (short_ad)
 *   sys/vfs/udf/udf_vnops.c lines 1125-1135 (long_ad)
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

/* ---- verbatim struct/macro replicas from sys/vfs/udf/ecma167-udf.h ---- */

struct short_ad { uint32_t len; uint32_t pos; } __attribute__((packed));
struct long_ad { uint32_t len; uint32_t lb_num; uint16_t part_num;
                 uint16_t ad_flags; uint32_t ad_id; } __attribute__((packed));

/* file_entry: only the tail fields we exercise are modeled.  data[] is the
 * extended-attribute + allocation-descriptor area; l_ea bytes of EA then
 * l_ad bytes of AD.  We allocate l_ea + l_ad + GUARD bytes and fill the
 * guard with a sentinel so an OOB read is unambiguous. */
struct file_entry {
    uint32_t l_ea;
    uint32_t l_ad;
    uint8_t  data[1];   /* [0..l_ea+l_ad) is the live region; beyond is guard */
};

/* verbatim from ecma167-udf.h:371 */
#define GETICB(ad_type, fentry, offset)   ((struct ad_type *)&(fentry)->data[offset])
#define GETICBLEN(ad_type, icb)           (((struct ad_type *)(icb))->len)

#define SHORT_AD_SIZE (sizeof(struct short_ad))   /* 8 */
#define LONG_AD_SIZE  (sizeof(struct long_ad))    /* 16 */

/* sentinel placed in the guard region; if the OOB read happens, the loop
 * will read this value as icblen and we detect it. */
#define OOB_SENTINEL_LEN 0xDEADBEEFu
#define OOB_SENTINEL_POS 0xCAFEBABEu

/*
 * Replica of udf_bmap_internal short_ad branch (udf_vnops.c:1101-1111).
 * Returns 0 on "found a sector", EINVAL on "out of bounds".
 * Sets *oob_read = 1 if it dereferenced past l_ad.
 */
static int
bmap_short_replica(struct file_entry *fentry, uint32_t offset,
                   uint32_t *sector, uint32_t *max_size, int *oob_read)
{
    void *icb;
    uint32_t icblen = 0;
    int ad_offset, ad_num = 0;

    *oob_read = 0;

    /* ---- VERBATIM kernel loop, sys/vfs/udf/udf_vnops.c:1101-1111 ---- */
    do {
        offset -= icblen;                                   /* line 1102 */
        ad_offset = sizeof(struct short_ad) * ad_num;       /* line 1103 */
        if (ad_offset > fentry->l_ad) {                     /* line 1104 BUG */
            return -1; /* "File offset out of bounds" -> EINVAL */
        }
        if (ad_offset + (int)sizeof(struct short_ad) > (int)(fentry->l_ea + fentry->l_ad) - (int)fentry->l_ea) {
            /* we are about to read sizeof(short_ad) bytes starting at
             * data[l_ea + ad_offset]; if that crosses l_ea+l_ad, it is OOB */
            if (ad_offset >= (int)fentry->l_ad) {
                *oob_read = 1;
            }
        }
        icb = GETICB(long_ad, fentry, fentry->l_ea + ad_offset); /* line 1108 */
        icblen = GETICBLEN(short_ad, icb);                  /* line 1109 */
        ad_num++;                                           /* line 1110 */
    } while (offset >= icblen);                             /* line 1111 */
    /* ---- end verbatim ---- */

    *sector   = ((struct short_ad *)icb)->pos;
    *max_size = icblen;
    return 0;
}

int
main(void)
{
    /* Allocate a file_entry with l_ea=0, l_ad=8 (exactly one short_ad).
     * Carve a guard region right after the live AD area and fill it with
     * the sentinel, so an OOB read of the next short_ad returns our
     * unmistakable marker. */
    size_t live = SHORT_AD_SIZE;                 /* 8 bytes of AD */
    size_t guard = SHORT_AD_SIZE;                /* 8 bytes of guard */
    /* data[] is declared [1]; allocate the real size ourselves */
    struct file_entry *fe = calloc(1, sizeof(*fe) + live + guard - 1);
    if (!fe) { perror("calloc"); return 2; }

    fe->l_ea = 0;
    fe->l_ad = live;                             /* 8 */

    /* the single valid short_ad: covers 2048 bytes at sector 5 */
    struct short_ad *ad0 = (struct short_ad *)&fe->data[0];
    ad0->len = 2048;
    ad0->pos = 5;

    /* guard: the next 8 bytes, which the buggy loop will read OOB */
    struct short_ad *guard_ad = (struct short_ad *)&fe->data[live];
    guard_ad->len = OOB_SENTINEL_LEN;
    guard_ad->pos = OOB_SENTINEL_POS;

    /* offset just past the first (and only) extent.  In the kernel this
     * is udf_read with uio_offset=2048 against a file whose inf_len>2048. */
    uint32_t sector = 0, max_size = 0, offset = 2048;
    int oob_read = 0;
    int rc = bmap_short_replica(fe, offset, &sector, &max_size, &oob_read);

    printf("DF-0832 udf_bmap_internal short_ad off-by-one harness\n");
    printf("  fentry: l_ea=%u l_ad=%u (== 1 * sizeof(short_ad)=%zu)\n",
           fe->l_ea, fe->l_ad, SHORT_AD_SIZE);
    printf("  short_ad[0]: len=%u pos=%u (covers bytes 0..2047)\n",
           ad0->len, ad0->pos);
    printf("  requested offset=%u (past the only extent)\n", offset);
    printf("  buggy-loop returned rc=%d  sector=0x%x  max_size=0x%x\n",
           rc, sector, max_size);
    printf("  OOB read detected: %s\n", oob_read ? "YES" : "no");

    if (oob_read) {
        printf("BUG: loop read sizeof(short_ad)=%zu bytes at ad_offset==l_ad,\n"
               "     i.e. &fentry->data[l_ea + l_ad] = ONE PAST the end of\n"
               "     the allocation-descriptor area.  On the kernel this is a\n"
               "     heap OOB read of 8 bytes (short_ad) / 16 bytes (long_ad).\n",
               SHORT_AD_SIZE);
        if (sector == OOB_SENTINEL_POS && max_size == OOB_SENTINEL_LEN) {
            printf("  CONFIRMED: returned sector/max_size are the guard sentinel\n"
                   "  (0x%x / 0x%x) — the loop consumed OOB bytes as a live AD.\n",
                   OOB_SENTINEL_POS, OOB_SENTINEL_LEN);
        }
        free(fe);
        return 1;
    }
    printf("OK: no OOB read (bound is tight).\n");
    free(fe);
    return 0;
}
