DF-0771 / fix.diff
diff --git a/sys/vfs/hammer/hammer_inode.c b/sys/vfs/hammer/hammer_inode.c --- a/sys/vfs/hammer/hammer_inode.c +++ b/sys/vfs/hammer/hammer_inode.c @@ -520,6 +520,31 @@ * information. Copy the information to the in-memory inode * and cache the B-Tree node to improve future operations. */ + /* + * DF-0771: Validate the on-disk inode record length before the + * unconditional 128-byte struct copy of inode_data below. + * + * A crafted image can set data_len != sizeof(struct hammer_inode_data) + * together with data_crc = 0 to bypass hammer_crc_test_leaf(): that + * helper returns 0 for a wrong-sized INODE record, which matches an + * on-disk data_crc of 0, so the CRC gate passes. The struct copy at + * hammer_inode.c:525 then reads sizeof(struct hammer_inode_data) bytes + * from cursor.data unconditionally; when data_offset's within-buffer + * offset (the low HAMMER_BUFMASK bits) is large, the copy runs past + * the 16 KiB data buffer into adjacent kernel heap (OOB read / info + * leak; page fault / panic when the adjacent page is unmapped). + * + * The only length check on the load path is the KKASSERT in + * hammer_btree_extract() which bounds data_len to HAMMER_XBUFSIZE, so + * reject an INODE record whose data_len is not exactly the struct size. + */ + if (*errorp == 0 && + cursor.leaf->data_len != sizeof(struct hammer_inode_data)) { + hdkprintf("bad inode data_len %d for obj_id=%016jx\n", + cursor.leaf->data_len, + (uintmax_t)cursor.leaf->base.obj_id); + *errorp = EIO; + } if (*errorp == 0) { ip->ino_leaf = cursor.node->ondisk->elms[cursor.index].leaf; ip->ino_data = cursor.data->inode; |