DragonFlyBSD Kernel Audit
DF-0771 / fix.diff
← back to finding ↓ download raw
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;