// https://syzkaller.appspot.com/bug?id=bc61acddc2dc3aae5c754c608d95a7ba80b540f0 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #define BTRFS_MAGIC 0x4D5F53665248425FULL #define BTRFS_CSUM_SIZE 32 #define BTRFS_FSID_SIZE 16 #define BTRFS_UUID_SIZE 16 #define BTRFS_SYSTEM_CHUNK_ARRAY_SIZE 2048 #define BTRFS_NUM_BACKUP_ROOTS 4 #define BTRFS_IMAGE_SIZE 0xf00000ULL /* 15 MiB */ #define SYS_CHUNK_OFFSET 0x100000ULL /* 1 MiB */ #define SYS_CHUNK_SIZE 0x100000ULL /* 1 MiB */ #define META_CHUNK_OFFSET 0x200000ULL /* 2 MiB */ #define META_CHUNK_SIZE 0x800000ULL /* 8 MiB */ #define DATA_CHUNK_OFFSET 0xa00000ULL /* 10 MiB */ #define DATA_CHUNK_SIZE 0x400000ULL /* 4 MiB */ #define INITIAL_DEV_BYTES_USED 0xe00000ULL /* 14 MiB, leaves exactly 1 MiB unallocated */ #define BTRFS_ROOT_TREE_OBJECTID 1ULL #define BTRFS_EXTENT_TREE_OBJECTID 2ULL #define BTRFS_CHUNK_TREE_OBJECTID 3ULL #define BTRFS_DEV_TREE_OBJECTID 4ULL #define BTRFS_FS_TREE_OBJECTID 5ULL #define BTRFS_ROOT_TREE_DIR_OBJECTID 6ULL #define BTRFS_CSUM_TREE_OBJECTID 7ULL #define BTRFS_UUID_TREE_OBJECTID 9ULL #define BTRFS_FREE_SPACE_TREE_OBJECTID 10ULL #define BTRFS_DATA_RELOC_TREE_OBJECTID ((uint64_t)-9ULL) #define BTRFS_INODE_ITEM_KEY 1 #define BTRFS_INODE_REF_KEY 12 #define BTRFS_DIR_ITEM_KEY 84 #define BTRFS_DIR_INDEX_KEY 96 #define BTRFS_ROOT_ITEM_KEY 132 #define BTRFS_EXTENT_ITEM_KEY 168 #define BTRFS_TREE_BLOCK_REF_KEY 176 #define BTRFS_BLOCK_GROUP_ITEM_KEY 192 #define BTRFS_FREE_SPACE_INFO_KEY 198 #define BTRFS_FREE_SPACE_EXTENT_KEY 199 #define BTRFS_DEV_EXTENT_KEY 204 #define BTRFS_DEV_ITEM_KEY 216 #define BTRFS_CHUNK_ITEM_KEY 228 #define BTRFS_HEADER_FLAG_WRITTEN (1ULL << 0) #define BTRFS_EXTENT_FLAG_TREE_BLOCK (1ULL << 1) #define BTRFS_BLOCK_GROUP_DATA (1ULL << 0) #define BTRFS_BLOCK_GROUP_SYSTEM (1ULL << 1) #define BTRFS_BLOCK_GROUP_METADATA (1ULL << 2) #define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE (1ULL << 0) #define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID (1ULL << 1) #define BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF (1ULL << 0) #define BTRFS_FT_REG_FILE 1 struct btrfs_disk_key { uint64_t objectid; uint8_t type; uint64_t offset; } __attribute__((packed)); struct btrfs_header { uint8_t csum[BTRFS_CSUM_SIZE]; uint8_t fsid[BTRFS_FSID_SIZE]; uint64_t bytenr; uint64_t flags; uint8_t chunk_tree_uuid[BTRFS_UUID_SIZE]; uint64_t generation; uint64_t owner; uint32_t nritems; uint8_t level; } __attribute__((packed)); struct btrfs_item { struct btrfs_disk_key key; uint32_t offset; uint32_t size; } __attribute__((packed)); struct btrfs_stripe { uint64_t devid; uint64_t offset; uint8_t dev_uuid[BTRFS_UUID_SIZE]; } __attribute__((packed)); struct btrfs_chunk { uint64_t length; uint64_t owner; uint64_t stripe_len; uint64_t type; uint32_t io_align; uint32_t io_width; uint32_t sector_size; uint16_t num_stripes; uint16_t sub_stripes; struct btrfs_stripe stripe; } __attribute__((packed)); struct btrfs_dev_item { uint64_t devid; uint64_t total_bytes; uint64_t bytes_used; uint32_t io_align; uint32_t io_width; uint32_t sector_size; uint64_t type; uint64_t generation; uint64_t start_offset; uint32_t dev_group; uint8_t seek_speed; uint8_t bandwidth; uint8_t uuid[BTRFS_UUID_SIZE]; uint8_t fsid[BTRFS_FSID_SIZE]; } __attribute__((packed)); struct btrfs_timespec { uint64_t sec; uint32_t nsec; } __attribute__((packed)); struct btrfs_inode_item { uint64_t generation; uint64_t transid; uint64_t size; uint64_t nbytes; uint64_t block_group; uint32_t nlink; uint32_t uid; uint32_t gid; uint32_t mode; uint64_t rdev; uint64_t flags; uint64_t sequence; uint64_t reserved[4]; struct btrfs_timespec atime; struct btrfs_timespec ctime; struct btrfs_timespec mtime; struct btrfs_timespec otime; } __attribute__((packed)); struct btrfs_root_item { struct btrfs_inode_item inode; uint64_t generation; uint64_t root_dirid; uint64_t bytenr; uint64_t byte_limit; uint64_t bytes_used; uint64_t last_snapshot; uint64_t flags; uint32_t refs; struct btrfs_disk_key drop_progress; uint8_t drop_level; uint8_t level; uint64_t generation_v2; uint8_t uuid[BTRFS_UUID_SIZE]; uint8_t parent_uuid[BTRFS_UUID_SIZE]; uint8_t received_uuid[BTRFS_UUID_SIZE]; uint64_t ctransid; uint64_t otransid; uint64_t stransid; uint64_t rtransid; struct btrfs_timespec ctime; struct btrfs_timespec otime; struct btrfs_timespec stime; struct btrfs_timespec rtime; uint64_t reserved[8]; } __attribute__((packed)); struct btrfs_block_group_item { uint64_t used; uint64_t chunk_objectid; uint64_t flags; } __attribute__((packed)); struct btrfs_free_space_info { uint32_t extent_count; uint32_t flags; } __attribute__((packed)); struct btrfs_dev_extent { uint64_t chunk_tree; uint64_t chunk_objectid; uint64_t chunk_offset; uint64_t length; uint8_t chunk_tree_uuid[BTRFS_UUID_SIZE]; } __attribute__((packed)); struct btrfs_inode_ref { uint64_t index; uint16_t name_len; } __attribute__((packed)); struct btrfs_dir_item { struct btrfs_disk_key location; uint64_t transid; uint16_t data_len; uint16_t name_len; uint8_t type; } __attribute__((packed)); struct btrfs_root_backup { uint64_t tree_root; uint64_t tree_root_gen; uint64_t chunk_root; uint64_t chunk_root_gen; uint64_t extent_root; uint64_t extent_root_gen; uint64_t fs_root; uint64_t fs_root_gen; uint64_t dev_root; uint64_t dev_root_gen; uint64_t csum_root; uint64_t csum_root_gen; uint64_t total_bytes; uint64_t bytes_used; uint64_t num_devices; uint64_t unused_64[4]; uint8_t tree_root_level; uint8_t chunk_root_level; uint8_t extent_root_level; uint8_t fs_root_level; uint8_t dev_root_level; uint8_t csum_root_level; uint8_t unused_8[10]; } __attribute__((packed)); struct btrfs_super_block { uint8_t csum[BTRFS_CSUM_SIZE]; uint8_t fsid[BTRFS_FSID_SIZE]; uint64_t bytenr; uint64_t flags; uint64_t magic; uint64_t generation; uint64_t root; uint64_t chunk_root; uint64_t log_root; uint64_t __unused_log_root_transid; uint64_t total_bytes; uint64_t bytes_used; uint64_t root_dir_objectid; uint64_t num_devices; uint32_t sectorsize; uint32_t nodesize; uint32_t __unused_leafsize; uint32_t stripesize; uint32_t sys_chunk_array_size; uint64_t chunk_root_generation; uint64_t compat_flags; uint64_t compat_ro_flags; uint64_t incompat_flags; uint16_t csum_type; uint8_t root_level; uint8_t chunk_root_level; uint8_t log_root_level; struct btrfs_dev_item dev_item; char label[256]; uint64_t cache_generation; uint64_t uuid_tree_generation; uint8_t metadata_uuid[BTRFS_FSID_SIZE]; uint64_t nr_global_roots; uint64_t remap_root; uint64_t remap_root_generation; uint8_t remap_root_level; uint8_t reserved[199]; uint8_t sys_chunk_array[BTRFS_SYSTEM_CHUNK_ARRAY_SIZE]; struct btrfs_root_backup super_roots[BTRFS_NUM_BACKUP_ROOTS]; uint8_t padding[565]; } __attribute__((packed)); struct tree_block_extent_payload { uint64_t refs; /* 1 */ uint64_t generation; /* 1 */ uint64_t flags; /* BTRFS_EXTENT_FLAG_TREE_BLOCK (2ULL) */ struct btrfs_disk_key first_key; /* all 0 */ uint8_t level; /* 0 */ uint8_t type; /* BTRFS_TREE_BLOCK_REF_KEY (176) */ uint64_t offset; /* owning root objectid */ } __attribute__((packed)); _Static_assert(sizeof(struct tree_block_extent_payload) == 51, "tree_block_extent_payload must be 51 bytes"); _Static_assert(sizeof(struct btrfs_free_space_info) == 8, "btrfs_free_space_info must be 8 bytes"); static uint32_t crc32c_table[256]; static void init_crc32c_table(void) { for (uint32_t i = 0; i < 256; i++) { uint32_t crc = i; for (int j = 0; j < 8; j++) { if (crc & 1) crc = (crc >> 1) ^ 0x82F63B78U; else crc >>= 1; } crc32c_table[i] = crc; } } static uint32_t btrfs_crc32c_raw(uint32_t crc, const void *data, size_t len) { const uint8_t *p = (const uint8_t *)data; for (size_t i = 0; i < len; i++) { crc = (crc >> 8) ^ crc32c_table[(crc & 0xFF) ^ p[i]]; } return crc; } static uint32_t btrfs_crc32c(const void *data, size_t len) { return ~btrfs_crc32c_raw(~0U, data, len); } static uint64_t btrfs_name_hash(const char *name, int len) { return (uint64_t)btrfs_crc32c_raw((uint32_t)~1, name, len); } struct leaf_builder { uint8_t buf[4096]; uint32_t nritems; uint32_t data_offset; }; static void leaf_init(struct leaf_builder *lb, uint64_t bytenr, uint64_t owner, const uint8_t *fsid) { memset(lb->buf, 0, 4096); struct btrfs_header *h = (struct btrfs_header *)lb->buf; memcpy(h->fsid, fsid, BTRFS_FSID_SIZE); h->bytenr = bytenr; h->flags = BTRFS_HEADER_FLAG_WRITTEN; h->generation = 1; h->owner = owner; h->nritems = 0; h->level = 0; lb->nritems = 0; lb->data_offset = 4096 - sizeof(struct btrfs_header); } static void leaf_add_item(struct leaf_builder *lb, uint64_t objectid, uint8_t type, uint64_t offset, const void *data, uint32_t size) { lb->data_offset -= size; struct btrfs_item *it = (struct btrfs_item *)(lb->buf + sizeof(struct btrfs_header) + lb->nritems * sizeof(struct btrfs_item)); it->key.objectid = objectid; it->key.type = type; it->key.offset = offset; it->offset = lb->data_offset; it->size = size; if (size > 0 && data != NULL) { memcpy(lb->buf + sizeof(struct btrfs_header) + lb->data_offset, data, size); } lb->nritems++; } static void leaf_finalize(struct leaf_builder *lb) { struct btrfs_header *h = (struct btrfs_header *)lb->buf; h->nritems = lb->nritems; uint32_t csum = btrfs_crc32c(lb->buf + 32, 4096 - 32); memcpy(h->csum, &csum, 4); } static void write_leaf_at(int fd, off_t offset, struct leaf_builder *lb) { leaf_finalize(lb); ssize_t written = pwrite(fd, lb->buf, 4096, offset); if (written != 4096) { printf("[-] Failed to pwrite leaf at offset 0x%lx: %s\n", (long)offset, strerror(errno)); exit(1); } } static void init_tree_block_payload(struct tree_block_extent_payload *p, uint64_t owner_root) { memset(p, 0, sizeof(*p)); p->refs = 1; p->generation = 1; p->flags = BTRFS_EXTENT_FLAG_TREE_BLOCK; p->level = 0; p->type = BTRFS_TREE_BLOCK_REF_KEY; p->offset = owner_root; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); init_crc32c_table(); printf("[*] Starting Btrfs reproducer for __add_block_group_free_space...\n"); const char *img_path = "/tmp/btrfs.img"; const char *mnt_path = "/tmp/btrfs_mnt"; umount2(mnt_path, MNT_FORCE | MNT_DETACH); int img_fd = open(img_path, O_RDWR | O_CREAT | O_TRUNC, 0644); if (img_fd < 0) { printf("[-] Failed to create %s: %s\n", img_path, strerror(errno)); exit(1); } printf("[+] Successfully created image file %s\n", img_path); /* * Physical Layout: * - Image size: 15 MiB (0xf00000) * - Reserved: [0, 0x100000) (1 MiB) * - SYSTEM chunk: [0x100000, 0x200000) (1 MiB) * - METADATA chunk: [0x200000, 0xa00000) (8 MiB) * - DATA chunk: [0xa00000, 0xe00000) (4 MiB) * - Unallocated device space: [0xe00000, 0xf00000) (exactly 1 MiB) * * Initial bytes_used is 14 MiB (0xe00000), leaving exactly 1 MiB unallocated. * METADATA is 8 MiB so global_block_rsv succeeds and metadata reclaim is NOT triggered. * Free Space Tree declares only 2 free leaves (0x208000..0x20a000) for METADATA. * When fallocate() requests data space on the 100% full DATA chunk, * async data space reclaim allocates the 1 MiB remaining disk space as a DATA chunk, * completely exhausting disk capacity. * Phase 2 pending block group creation consumes the 2 free metadata leaves, * and when __add_block_group_free_space attempts to add the new free space info item, * tree block allocation fails with -ENOSPC, triggering the target warning at line 1446. */ int res = ftruncate(img_fd, BTRFS_IMAGE_SIZE); if (res < 0) { printf("[-] Failed to ftruncate image file to 0x%llx: %s\n", (unsigned long long)BTRFS_IMAGE_SIZE, strerror(errno)); close(img_fd); exit(1); } printf("[+] Image file truncated to 0x%llx bytes.\n", (unsigned long long)BTRFS_IMAGE_SIZE); uint8_t fsid[BTRFS_FSID_SIZE] = { 0x5a, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; uint8_t dev_uuid[BTRFS_UUID_SIZE] = { 0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c }; /* Build Superblock at offset 0x10000 */ uint8_t sb_buf[4096]; memset(sb_buf, 0, sizeof(sb_buf)); struct btrfs_super_block *sb = (struct btrfs_super_block *)sb_buf; memcpy(sb->fsid, fsid, BTRFS_FSID_SIZE); sb->bytenr = 0x10000; sb->flags = 0; sb->magic = BTRFS_MAGIC; sb->generation = 1; sb->root = META_CHUNK_OFFSET; /* Root Tree leaf at 0x200000 */ sb->chunk_root = SYS_CHUNK_OFFSET; /* Chunk Tree leaf at 0x100000 */ sb->log_root = 0; sb->total_bytes = BTRFS_IMAGE_SIZE; /* 15 MiB */ sb->bytes_used = 9 * 4096; /* 36864 bytes: 8 metadata leaves + 1 system leaf */ sb->root_dir_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID; sb->num_devices = 1; sb->sectorsize = 4096; sb->nodesize = 4096; sb->__unused_leafsize = 4096; sb->stripesize = 4096; sb->chunk_root_generation = 1; sb->compat_flags = 0; sb->compat_ro_flags = BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE | BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID; sb->incompat_flags = BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF; sb->csum_type = 0; /* CRC32C */ sb->root_level = 0; sb->chunk_root_level = 0; sb->log_root_level = 0; sb->cache_generation = 0; /* Prevents v1 space cache cleanup */ sb->uuid_tree_generation = 1; /* Prevents UUID tree recreation */ memcpy(sb->metadata_uuid, fsid, BTRFS_FSID_SIZE); /* Superblock dev_item */ sb->dev_item.devid = 1; sb->dev_item.total_bytes = BTRFS_IMAGE_SIZE; sb->dev_item.bytes_used = INITIAL_DEV_BYTES_USED; /* 14 MiB */ sb->dev_item.io_align = 4096; sb->dev_item.io_width = 4096; sb->dev_item.sector_size = 4096; sb->dev_item.type = 0; sb->dev_item.generation = 1; sb->dev_item.start_offset = 0; sb->dev_item.dev_group = 0; memcpy(sb->dev_item.uuid, dev_uuid, BTRFS_UUID_SIZE); memcpy(sb->dev_item.fsid, fsid, BTRFS_FSID_SIZE); /* sys_chunk_array: SYSTEM chunk (length 1 MiB at physical 0x100000, logical 0x100000) */ struct btrfs_disk_key sys_key; sys_key.objectid = 256; sys_key.type = BTRFS_CHUNK_ITEM_KEY; sys_key.offset = SYS_CHUNK_OFFSET; struct btrfs_chunk sys_chunk; memset(&sys_chunk, 0, sizeof(sys_chunk)); sys_chunk.length = SYS_CHUNK_SIZE; sys_chunk.owner = BTRFS_EXTENT_TREE_OBJECTID; sys_chunk.stripe_len = 65536; sys_chunk.type = BTRFS_BLOCK_GROUP_SYSTEM; sys_chunk.io_align = 4096; sys_chunk.io_width = 4096; sys_chunk.sector_size = 4096; sys_chunk.num_stripes = 1; sys_chunk.sub_stripes = 0; sys_chunk.stripe.devid = 1; sys_chunk.stripe.offset = SYS_CHUNK_OFFSET; memcpy(sys_chunk.stripe.dev_uuid, dev_uuid, BTRFS_UUID_SIZE); memcpy(sb->sys_chunk_array, &sys_key, sizeof(sys_key)); memcpy(sb->sys_chunk_array + sizeof(sys_key), &sys_chunk, sizeof(sys_chunk)); sb->sys_chunk_array_size = sizeof(sys_key) + sizeof(sys_chunk); uint32_t sb_csum = btrfs_crc32c(sb_buf + 32, 4096 - 32); memcpy(sb->csum, &sb_csum, 4); ssize_t written = pwrite(img_fd, sb_buf, 4096, 0x10000); if (written != 4096) { printf("[-] Failed to pwrite superblock: %s\n", strerror(errno)); exit(1); } printf("[+] Superblock written successfully at 0x10000.\n"); /* 1. Leaf 0: Chunk Tree (bytenr = 0x100000, owner = 3) */ struct leaf_builder lb; leaf_init(&lb, SYS_CHUNK_OFFSET, BTRFS_CHUNK_TREE_OBJECTID, fsid); /* Dev item: (1, BTRFS_DEV_ITEM_KEY=216, 1) */ leaf_add_item(&lb, 1, BTRFS_DEV_ITEM_KEY, 1, &sb->dev_item, sizeof(struct btrfs_dev_item)); /* SYSTEM chunk: (256, BTRFS_CHUNK_ITEM_KEY=228, 0x100000), len 1 MiB */ leaf_add_item(&lb, 256, BTRFS_CHUNK_ITEM_KEY, SYS_CHUNK_OFFSET, &sys_chunk, sizeof(struct btrfs_chunk)); /* METADATA chunk: (256, BTRFS_CHUNK_ITEM_KEY=228, 0x200000), len 8 MiB (0x800000) */ struct btrfs_chunk meta_chunk = sys_chunk; meta_chunk.length = META_CHUNK_SIZE; meta_chunk.type = BTRFS_BLOCK_GROUP_METADATA; meta_chunk.stripe.offset = META_CHUNK_OFFSET; leaf_add_item(&lb, 256, BTRFS_CHUNK_ITEM_KEY, META_CHUNK_OFFSET, &meta_chunk, sizeof(struct btrfs_chunk)); /* DATA chunk: (256, BTRFS_CHUNK_ITEM_KEY=228, 0xa00000), len 4 MiB (0x400000) */ struct btrfs_chunk data_chunk = sys_chunk; data_chunk.length = DATA_CHUNK_SIZE; data_chunk.type = BTRFS_BLOCK_GROUP_DATA; data_chunk.stripe.offset = DATA_CHUNK_OFFSET; leaf_add_item(&lb, 256, BTRFS_CHUNK_ITEM_KEY, DATA_CHUNK_OFFSET, &data_chunk, sizeof(struct btrfs_chunk)); write_leaf_at(img_fd, SYS_CHUNK_OFFSET, &lb); printf("[+] Chunk Tree leaf written at 0x%llx.\n", (unsigned long long)SYS_CHUNK_OFFSET); /* 2. Leaf 1: Root Tree (bytenr = 0x200000, owner = 1) */ leaf_init(&lb, META_CHUNK_OFFSET, BTRFS_ROOT_TREE_OBJECTID, fsid); struct btrfs_root_item ri; memset(&ri, 0, sizeof(ri)); ri.inode.generation = 1; ri.inode.transid = 1; ri.inode.nlink = 1; ri.generation = 1; ri.generation_v2 = 1; ri.refs = 1; ri.level = 0; ri.bytes_used = 4096; /* Items strictly sorted by (objectid, type, offset) */ ri.bytenr = 0x201000; leaf_add_item(&lb, BTRFS_EXTENT_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); ri.bytenr = 0x202000; leaf_add_item(&lb, BTRFS_DEV_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); ri.bytenr = 0x203000; ri.root_dirid = 256; leaf_add_item(&lb, BTRFS_FS_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); ri.root_dirid = 0; ri.bytenr = 0x204000; leaf_add_item(&lb, BTRFS_CSUM_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); ri.bytenr = 0x207000; leaf_add_item(&lb, BTRFS_UUID_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); ri.bytenr = 0x205000; leaf_add_item(&lb, BTRFS_FREE_SPACE_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); ri.bytenr = 0x206000; leaf_add_item(&lb, BTRFS_DATA_RELOC_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, 0, &ri, sizeof(ri)); write_leaf_at(img_fd, META_CHUNK_OFFSET, &lb); printf("[+] Root Tree leaf written at 0x%llx.\n", (unsigned long long)META_CHUNK_OFFSET); /* 3. Leaf 2: Extent Tree (bytenr = 0x201000, owner = 2) */ leaf_init(&lb, 0x201000, BTRFS_EXTENT_TREE_OBJECTID, fsid); struct tree_block_extent_payload ep; struct btrfs_block_group_item bgi; /* 1. (0x100000, EXTENT_ITEM_KEY=168, 4096): Chunk Tree leaf */ init_tree_block_payload(&ep, BTRFS_CHUNK_TREE_OBJECTID); leaf_add_item(&lb, SYS_CHUNK_OFFSET, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 2. (0x100000, BLOCK_GROUP_ITEM_KEY=192, 0x100000): SYSTEM BG */ bgi.used = 4096; bgi.chunk_objectid = 256; bgi.flags = BTRFS_BLOCK_GROUP_SYSTEM; leaf_add_item(&lb, SYS_CHUNK_OFFSET, BTRFS_BLOCK_GROUP_ITEM_KEY, SYS_CHUNK_SIZE, &bgi, sizeof(bgi)); /* 3. (0x200000, EXTENT_ITEM_KEY=168, 4096): Root Tree leaf */ init_tree_block_payload(&ep, BTRFS_ROOT_TREE_OBJECTID); leaf_add_item(&lb, META_CHUNK_OFFSET, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 4. (0x200000, BLOCK_GROUP_ITEM_KEY=192, 0x800000): METADATA BG, length 8 MiB, used 32 KiB */ bgi.used = 0x8000; bgi.chunk_objectid = 256; bgi.flags = BTRFS_BLOCK_GROUP_METADATA; leaf_add_item(&lb, META_CHUNK_OFFSET, BTRFS_BLOCK_GROUP_ITEM_KEY, META_CHUNK_SIZE, &bgi, sizeof(bgi)); /* 5. (0x201000, EXTENT_ITEM_KEY=168, 4096): Extent Tree leaf */ init_tree_block_payload(&ep, BTRFS_EXTENT_TREE_OBJECTID); leaf_add_item(&lb, 0x201000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 6. (0x202000, EXTENT_ITEM_KEY=168, 4096): Dev Tree leaf */ init_tree_block_payload(&ep, BTRFS_DEV_TREE_OBJECTID); leaf_add_item(&lb, 0x202000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 7. (0x203000, EXTENT_ITEM_KEY=168, 4096): FS Tree leaf */ init_tree_block_payload(&ep, BTRFS_FS_TREE_OBJECTID); leaf_add_item(&lb, 0x203000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 8. (0x204000, EXTENT_ITEM_KEY=168, 4096): Csum Tree leaf */ init_tree_block_payload(&ep, BTRFS_CSUM_TREE_OBJECTID); leaf_add_item(&lb, 0x204000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 9. (0x205000, EXTENT_ITEM_KEY=168, 4096): Free Space Tree leaf */ init_tree_block_payload(&ep, BTRFS_FREE_SPACE_TREE_OBJECTID); leaf_add_item(&lb, 0x205000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 10. (0x206000, EXTENT_ITEM_KEY=168, 4096): Data Reloc Tree leaf */ init_tree_block_payload(&ep, BTRFS_DATA_RELOC_TREE_OBJECTID); leaf_add_item(&lb, 0x206000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 11. (0x207000, EXTENT_ITEM_KEY=168, 4096): UUID Tree leaf */ init_tree_block_payload(&ep, BTRFS_UUID_TREE_OBJECTID); leaf_add_item(&lb, 0x207000, BTRFS_EXTENT_ITEM_KEY, 4096, &ep, sizeof(ep)); /* 12. (0xa00000, BLOCK_GROUP_ITEM_KEY=192, 0x400000): DATA BG, fully used (4 MiB) */ bgi.used = DATA_CHUNK_SIZE; bgi.chunk_objectid = 256; bgi.flags = BTRFS_BLOCK_GROUP_DATA; leaf_add_item(&lb, DATA_CHUNK_OFFSET, BTRFS_BLOCK_GROUP_ITEM_KEY, DATA_CHUNK_SIZE, &bgi, sizeof(bgi)); write_leaf_at(img_fd, 0x201000, &lb); printf("[+] Extent Tree leaf written at 0x201000 (12 items).\n"); /* 4. Leaf 3: Dev Tree (bytenr = 0x202000, owner = 4) */ leaf_init(&lb, 0x202000, BTRFS_DEV_TREE_OBJECTID, fsid); struct btrfs_dev_extent de; memset(&de, 0, sizeof(de)); de.chunk_tree = BTRFS_CHUNK_TREE_OBJECTID; de.chunk_objectid = 256; /* Dev extent for SYSTEM: 0x100000, len 1 MiB */ de.chunk_offset = SYS_CHUNK_OFFSET; de.length = SYS_CHUNK_SIZE; leaf_add_item(&lb, 1, BTRFS_DEV_EXTENT_KEY, SYS_CHUNK_OFFSET, &de, sizeof(de)); /* Dev extent for METADATA: 0x200000, len 8 MiB (0x800000) */ de.chunk_offset = META_CHUNK_OFFSET; de.length = META_CHUNK_SIZE; leaf_add_item(&lb, 1, BTRFS_DEV_EXTENT_KEY, META_CHUNK_OFFSET, &de, sizeof(de)); /* Dev extent for DATA: 0xa00000, len 4 MiB (0x400000) */ de.chunk_offset = DATA_CHUNK_OFFSET; de.length = DATA_CHUNK_SIZE; leaf_add_item(&lb, 1, BTRFS_DEV_EXTENT_KEY, DATA_CHUNK_OFFSET, &de, sizeof(de)); /* Dev item: (1, BTRFS_DEV_ITEM_KEY=216, 1) */ leaf_add_item(&lb, 1, BTRFS_DEV_ITEM_KEY, 1, &sb->dev_item, sizeof(struct btrfs_dev_item)); write_leaf_at(img_fd, 0x202000, &lb); printf("[+] Dev Tree leaf written at 0x202000.\n"); /* 5. Leaf 4: FS Tree (bytenr = 0x203000, owner = 5) */ leaf_init(&lb, 0x203000, BTRFS_FS_TREE_OBJECTID, fsid); struct btrfs_inode_item dir_ino; memset(&dir_ino, 0, sizeof(dir_ino)); dir_ino.generation = 1; dir_ino.transid = 1; dir_ino.size = 0; dir_ino.nbytes = 0; dir_ino.nlink = 1; dir_ino.mode = S_IFDIR | 0755; leaf_add_item(&lb, 256, BTRFS_INODE_ITEM_KEY, 0, &dir_ino, sizeof(dir_ino)); uint8_t iref_buf[sizeof(struct btrfs_inode_ref) + 2]; struct btrfs_inode_ref *iref = (struct btrfs_inode_ref *)iref_buf; iref->index = 0; iref->name_len = 2; memcpy(iref_buf + sizeof(struct btrfs_inode_ref), "..", 2); leaf_add_item(&lb, 256, BTRFS_INODE_REF_KEY, 256, iref_buf, sizeof(iref_buf)); uint8_t dentry_buf[sizeof(struct btrfs_dir_item) + 1]; struct btrfs_dir_item *di = (struct btrfs_dir_item *)dentry_buf; di->location.objectid = 257; di->location.type = BTRFS_INODE_ITEM_KEY; di->location.offset = 0; di->transid = 1; di->data_len = 0; di->name_len = 1; di->type = BTRFS_FT_REG_FILE; dentry_buf[sizeof(struct btrfs_dir_item)] = 'f'; uint64_t hash_f = btrfs_name_hash("f", 1); leaf_add_item(&lb, 256, BTRFS_DIR_ITEM_KEY, hash_f, dentry_buf, sizeof(dentry_buf)); leaf_add_item(&lb, 256, BTRFS_DIR_INDEX_KEY, 2, dentry_buf, sizeof(dentry_buf)); struct btrfs_inode_item file_ino; memset(&file_ino, 0, sizeof(file_ino)); file_ino.generation = 1; file_ino.transid = 1; file_ino.size = 0; file_ino.nbytes = 0; file_ino.nlink = 1; file_ino.mode = S_IFREG | 0644; leaf_add_item(&lb, 257, BTRFS_INODE_ITEM_KEY, 0, &file_ino, sizeof(file_ino)); uint8_t file_ref_buf[sizeof(struct btrfs_inode_ref) + 1]; struct btrfs_inode_ref *fref = (struct btrfs_inode_ref *)file_ref_buf; fref->index = 2; fref->name_len = 1; file_ref_buf[sizeof(struct btrfs_inode_ref)] = 'f'; leaf_add_item(&lb, 257, BTRFS_INODE_REF_KEY, 256, file_ref_buf, sizeof(file_ref_buf)); write_leaf_at(img_fd, 0x203000, &lb); printf("[+] FS Tree leaf written at 0x203000.\n"); /* 6. Leaf 5: CSUM Tree (bytenr = 0x204000, owner = 7, empty leaf) */ leaf_init(&lb, 0x204000, BTRFS_CSUM_TREE_OBJECTID, fsid); write_leaf_at(img_fd, 0x204000, &lb); printf("[+] CSUM Tree leaf written at 0x204000.\n"); /* 7. Leaf 6: Free Space Tree (bytenr = 0x205000, owner = 10) * Items strictly sorted by (objectid, type, offset): * 1. (0x100000, BTRFS_FREE_SPACE_INFO_KEY=198, 0x100000): SYSTEM info (extent_count=1) * 2. (0x101000, BTRFS_FREE_SPACE_EXTENT_KEY=199, 0xff000): SYSTEM free space extent * 3. (0x200000, BTRFS_FREE_SPACE_INFO_KEY=198, 0x800000): METADATA info (extent_count=1) * 4. (0x208000, BTRFS_FREE_SPACE_EXTENT_KEY=199, 0x2000): METADATA free space extent (2 leaves: 0x208000, 0x209000) * 5. (0xa00000, BTRFS_FREE_SPACE_INFO_KEY=198, 0x400000): DATA info (extent_count=0, 100% full) */ leaf_init(&lb, 0x205000, BTRFS_FREE_SPACE_TREE_OBJECTID, fsid); struct btrfs_free_space_info fsi; /* 1. SYSTEM Free Space Info */ memset(&fsi, 0, sizeof(fsi)); fsi.extent_count = 1; fsi.flags = 0; leaf_add_item(&lb, SYS_CHUNK_OFFSET, BTRFS_FREE_SPACE_INFO_KEY, SYS_CHUNK_SIZE, &fsi, sizeof(fsi)); /* 2. SYSTEM Free Space Extent */ leaf_add_item(&lb, SYS_CHUNK_OFFSET + 4096, BTRFS_FREE_SPACE_EXTENT_KEY, SYS_CHUNK_SIZE - 4096, NULL, 0); /* 3. METADATA Free Space Info */ memset(&fsi, 0, sizeof(fsi)); fsi.extent_count = 1; fsi.flags = 0; leaf_add_item(&lb, META_CHUNK_OFFSET, BTRFS_FREE_SPACE_INFO_KEY, META_CHUNK_SIZE, &fsi, sizeof(fsi)); /* 4. METADATA Free Space Extent (exactly 2 free leaves remaining: 0x208000 and 0x209000) */ leaf_add_item(&lb, 0x208000, BTRFS_FREE_SPACE_EXTENT_KEY, 0x2000, NULL, 0); /* 5. DATA Free Space Info (100% full) */ memset(&fsi, 0, sizeof(fsi)); fsi.extent_count = 0; fsi.flags = 0; leaf_add_item(&lb, DATA_CHUNK_OFFSET, BTRFS_FREE_SPACE_INFO_KEY, DATA_CHUNK_SIZE, &fsi, sizeof(fsi)); write_leaf_at(img_fd, 0x205000, &lb); printf("[+] Free Space Tree leaf written at 0x205000 (5 items).\n"); /* 8. Leaf 7: Data Reloc Tree (bytenr = 0x206000, owner = -9ULL) */ leaf_init(&lb, 0x206000, BTRFS_DATA_RELOC_TREE_OBJECTID, fsid); leaf_add_item(&lb, 256, BTRFS_INODE_ITEM_KEY, 0, &dir_ino, sizeof(dir_ino)); leaf_add_item(&lb, 256, BTRFS_INODE_REF_KEY, 256, iref_buf, sizeof(iref_buf)); write_leaf_at(img_fd, 0x206000, &lb); printf("[+] Data Reloc Tree leaf written at 0x206000.\n"); /* 9. Leaf 8: UUID Tree (bytenr = 0x207000, owner = 9ULL, empty leaf) */ leaf_init(&lb, 0x207000, BTRFS_UUID_TREE_OBJECTID, fsid); write_leaf_at(img_fd, 0x207000, &lb); printf("[+] UUID Tree leaf written at 0x207000.\n"); res = fsync(img_fd); if (res < 0) { printf("[-] Failed to fsync image file: %s\n", strerror(errno)); close(img_fd); exit(1); } close(img_fd); printf("[+] Btrfs image file written successfully.\n"); /* Setup loop device */ int ctl_fd = open("/dev/loop-control", O_RDWR); if (ctl_fd < 0) { printf("[-] Failed to open /dev/loop-control: %s\n", strerror(errno)); exit(1); } int dev_nr = ioctl(ctl_fd, LOOP_CTL_GET_FREE); close(ctl_fd); if (dev_nr < 0 || dev_nr >= 32) { dev_nr = 0; } printf("[+] Acquired loop device index: %d\n", dev_nr); char loop_path[64]; snprintf(loop_path, sizeof(loop_path), "/dev/loop%d", dev_nr); int loop_fd = open(loop_path, O_RDWR); if (loop_fd < 0) { printf("[-] Failed to open %s: %s\n", loop_path, strerror(errno)); exit(1); } /* Detach any stale mapping */ ioctl(loop_fd, LOOP_CLR_FD, 0); int img_ro_fd = open(img_path, O_RDWR); if (img_ro_fd < 0) { printf("[-] Failed to re-open %s: %s\n", img_path, strerror(errno)); close(loop_fd); exit(1); } res = ioctl(loop_fd, LOOP_SET_FD, img_ro_fd); if (res < 0) { printf("[-] Failed to ioctl(LOOP_SET_FD): %s\n", strerror(errno)); close(img_ro_fd); close(loop_fd); exit(1); } printf("[+] Attached %s to %s.\n", img_path, loop_path); /* Close descriptors prior to mount to prevent BLK_OPEN_RESTRICT_WRITES EBUSY */ close(img_ro_fd); close(loop_fd); printf("[+] Descriptors closed cleanly prior to mount.\n"); res = mkdir(mnt_path, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", mnt_path, strerror(errno)); exit(1); } res = mount(loop_path, mnt_path, "btrfs", 0, "space_cache=v2"); if (res < 0) { printf("[*] mount with space_cache=v2 returned %s, trying default options...\n", strerror(errno)); res = mount(loop_path, mnt_path, "btrfs", 0, NULL); } if (res < 0) { printf("[-] Failed to mount %s on %s: %s\n", loop_path, mnt_path, strerror(errno)); exit(1); } printf("[+] Successfully mounted %s on %s.\n", loop_path, mnt_path); /* Open the pre-created target file "f" with O_RDWR */ char file_path[128]; snprintf(file_path, sizeof(file_path), "%s/f", mnt_path); int fd = open(file_path, O_RDWR); if (fd < 0) { printf("[-] Failed to open %s: %s\n", file_path, strerror(errno)); exit(1); } printf("[+] Successfully opened %s (fd = %d).\n", file_path, fd); /* * Call fallocate(fd, 0, 0, 4 * 1024 * 1024). * * Calling fallocate requests 4 MiB data space on a 100% full DATA block group. * can_overcommit() returns false for DATA space, queuing async_data_reclaim_work * (btrfs_async_reclaim_data_space). * The kworker runs: * btrfs_async_reclaim_data_space * -> do_async_reclaim_data_space * -> flush_space(ALLOC_CHUNK_FORCE) * -> btrfs_chunk_alloc(CHUNK_ALLOC_FORCE) * allocates the 1 MiB remaining unallocated disk space as a DATA chunk. * -> btrfs_end_transaction * -> btrfs_create_pending_block_groups * -> insert_block_group_item (consumes free metadata leaf 0x208000) * -> btrfs_chunk_alloc_add_chunk_item (updates chunk tree in SYSTEM chunk) * -> insert_dev_extents (consumes free metadata leaf 0x209000) * -> btrfs_add_block_group_free_space * -> __add_block_group_free_space * -> add_new_free_space_info * * Inside add_new_free_space_info, inserting BTRFS_FREE_SPACE_INFO_KEY requires * COWing the Free Space Tree root leaf. * With 0 metadata leaves remaining and 0 unallocated bytes left on the device, * btrfs_alloc_tree_block fails with -ENOSPC. * This triggers btrfs_abort_transaction(trans, -ENOSPC) at fs/btrfs/free-space-tree.c:1446! */ printf("[*] Invoking fallocate() to request data space...\n"); int fa = fallocate(fd, 0, 0, 4 * 1024 * 1024); if (fa < 0) { printf("[*] fallocate() returned error (expected if transaction aborted): %s\n", strerror(errno)); } else { printf("[*] fallocate() returned: %d\n", fa); } close(fd); printf("[+] Closed file descriptor.\n"); /* Sleep to allow the asynchronous background kworker to complete and emit warning */ printf("[*] Sleeping 5 seconds to allow asynchronous reclaim to trigger the warning...\n"); sleep(5); printf("[+] Reproducer finished.\n"); return 0; }