Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

hfsplus: refactor b-tree map page access and add node-type validation

In HFS+ b-trees, the node allocation bitmap is stored across multiple
records. The first chunk resides in the b-tree Header Node at record
index 2, while all subsequent chunks are stored in dedicated Map Nodes
at record index 0.

This structural quirk forces callers like hfs_bmap_alloc() and
hfs_bmap_free() to duplicate boilerplate code to validate offsets, correct
lengths, and map the underlying pages via kmap_local_page(). There is
also currently no strict node-type validation before reading these
records, leaving the allocator vulnerable if a corrupted image points a
map linkage to an Index or Leaf node.

Introduce a unified bit-level API to encapsulate the map record access:
1. A new `struct hfs_bmap_ctx` to cleanly pass state and safely handle
page math across all architectures.
2. `hfs_bmap_get_map_page()`: Automatically validates node types
(HFS_NODE_HEADER vs HFS_NODE_MAP), infers the correct record index,
handles page-boundary math, and returns the unmapped `struct page *`
directly to the caller to avoid asymmetric mappings.
3. `hfs_bmap_clear_bit()`: A clean wrapper that internally handles page
mapping/unmapping for single-bit operations.

Refactor hfs_bmap_alloc() and hfs_bmap_free() to utilize this new API.
This deduplicates the allocator logic, hardens the map traversal against
fuzzed images, and provides the exact abstractions needed for upcoming
mount-time validation checks.

Signed-off-by: Shardul Bankar <shardul.b@mpiricsoftware.com>
Reviewed-by: Viacheslav Dubeyko <slava@dubeyko.com>
Tested-by: Viacheslav Dubeyko <slava@dubeyko.com>
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Link: https://lore.kernel.org/r/20260318073823.3933718-2-shardul.b@mpiricsoftware.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>

authored by

Shardul Bankar and committed by
Viacheslav Dubeyko
a8eed0ba b099ed59

+124 -47
+122 -47
fs/hfsplus/btree.c
··· 129 129 return clump_size; 130 130 } 131 131 132 + /* Context for iterating b-tree map pages 133 + * @page_idx: The index of the page within the b-node's page array 134 + * @off: The byte offset within the mapped page 135 + * @len: The remaining length of the map record 136 + */ 137 + struct hfs_bmap_ctx { 138 + unsigned int page_idx; 139 + unsigned int off; 140 + u16 len; 141 + }; 142 + 143 + /* 144 + * Finds the specific page containing the requested byte offset within the map 145 + * record. Automatically handles the difference between header and map nodes. 146 + * Returns the struct page pointer, or an ERR_PTR on failure. 147 + * Note: The caller is responsible for mapping/unmapping the returned page. 148 + */ 149 + static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node, struct hfs_bmap_ctx *ctx, 150 + u32 byte_offset) 151 + { 152 + u16 rec_idx, off16; 153 + unsigned int page_off; 154 + 155 + if (node->this == HFSPLUS_TREE_HEAD) { 156 + if (node->type != HFS_NODE_HEADER) { 157 + pr_err("hfsplus: invalid btree header node\n"); 158 + return ERR_PTR(-EIO); 159 + } 160 + rec_idx = HFSPLUS_BTREE_HDR_MAP_REC_INDEX; 161 + } else { 162 + if (node->type != HFS_NODE_MAP) { 163 + pr_err("hfsplus: invalid btree map node\n"); 164 + return ERR_PTR(-EIO); 165 + } 166 + rec_idx = HFSPLUS_BTREE_MAP_NODE_REC_INDEX; 167 + } 168 + 169 + ctx->len = hfs_brec_lenoff(node, rec_idx, &off16); 170 + if (!ctx->len) 171 + return ERR_PTR(-ENOENT); 172 + 173 + if (!is_bnode_offset_valid(node, off16)) 174 + return ERR_PTR(-EIO); 175 + 176 + ctx->len = check_and_correct_requested_length(node, off16, ctx->len); 177 + 178 + if (byte_offset >= ctx->len) 179 + return ERR_PTR(-EINVAL); 180 + 181 + page_off = (u32)off16 + node->page_offset + byte_offset; 182 + ctx->page_idx = page_off >> PAGE_SHIFT; 183 + ctx->off = page_off & ~PAGE_MASK; 184 + 185 + return node->page[ctx->page_idx]; 186 + } 187 + 188 + /** 189 + * hfs_bmap_clear_bit - clear a bit in the b-tree map 190 + * @node: the b-tree node containing the map record 191 + * @node_bit_idx: the relative bit index within the node's map record 192 + * 193 + * Returns 0 on success, -EINVAL if already clear, or negative error code. 194 + */ 195 + static int hfs_bmap_clear_bit(struct hfs_bnode *node, u32 node_bit_idx) 196 + { 197 + struct hfs_bmap_ctx ctx; 198 + struct page *page; 199 + u8 *bmap, mask; 200 + 201 + page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE); 202 + if (IS_ERR(page)) 203 + return PTR_ERR(page); 204 + 205 + bmap = kmap_local_page(page); 206 + 207 + mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE)); 208 + 209 + if (!(bmap[ctx.off] & mask)) { 210 + kunmap_local(bmap); 211 + return -EINVAL; 212 + } 213 + 214 + bmap[ctx.off] &= ~mask; 215 + set_page_dirty(page); 216 + kunmap_local(bmap); 217 + 218 + return 0; 219 + } 220 + 132 221 /* Get a reference to a B*Tree and do some initial checks */ 133 222 struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id) 134 223 { ··· 463 374 struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) 464 375 { 465 376 struct hfs_bnode *node, *next_node; 466 - struct page **pagep; 377 + struct hfs_bmap_ctx ctx; 378 + struct page *page; 467 379 u32 nidx, idx; 468 - unsigned off; 469 - u16 off16; 470 - u16 len; 471 380 u8 *data, byte, m; 472 381 int i, res; 473 382 ··· 477 390 node = hfs_bnode_find(tree, nidx); 478 391 if (IS_ERR(node)) 479 392 return node; 480 - len = hfs_brec_lenoff(node, 2, &off16); 481 - off = off16; 482 393 483 - if (!is_bnode_offset_valid(node, off)) { 394 + page = hfs_bmap_get_map_page(node, &ctx, 0); 395 + if (IS_ERR(page)) { 396 + res = PTR_ERR(page); 484 397 hfs_bnode_put(node); 485 - return ERR_PTR(-EIO); 398 + return ERR_PTR(res); 486 399 } 487 - len = check_and_correct_requested_length(node, off, len); 488 400 489 - off += node->page_offset; 490 - pagep = node->page + (off >> PAGE_SHIFT); 491 - data = kmap_local_page(*pagep); 492 - off &= ~PAGE_MASK; 401 + data = kmap_local_page(page); 493 402 idx = 0; 494 403 495 404 for (;;) { 496 - while (len) { 497 - byte = data[off]; 405 + while (ctx.len) { 406 + byte = data[ctx.off]; 498 407 if (byte != 0xff) { 499 408 for (m = 0x80, i = 0; i < 8; m >>= 1, i++) { 500 409 if (!(byte & m)) { 501 410 idx += i; 502 - data[off] |= m; 503 - set_page_dirty(*pagep); 411 + data[ctx.off] |= m; 412 + set_page_dirty(page); 504 413 kunmap_local(data); 505 414 tree->free_nodes--; 506 415 mark_inode_dirty(tree->inode); ··· 506 423 } 507 424 } 508 425 } 509 - if (++off >= PAGE_SIZE) { 426 + if (++ctx.off >= PAGE_SIZE) { 510 427 kunmap_local(data); 511 - data = kmap_local_page(*++pagep); 512 - off = 0; 428 + page = node->page[++ctx.page_idx]; 429 + data = kmap_local_page(page); 430 + ctx.off = 0; 513 431 } 514 432 idx += 8; 515 - len--; 433 + ctx.len--; 516 434 } 517 435 kunmap_local(data); 518 436 nidx = node->next; ··· 527 443 return next_node; 528 444 node = next_node; 529 445 530 - len = hfs_brec_lenoff(node, 0, &off16); 531 - off = off16; 532 - off += node->page_offset; 533 - pagep = node->page + (off >> PAGE_SHIFT); 534 - data = kmap_local_page(*pagep); 535 - off &= ~PAGE_MASK; 446 + page = hfs_bmap_get_map_page(node, &ctx, 0); 447 + if (IS_ERR(page)) { 448 + res = PTR_ERR(page); 449 + hfs_bnode_put(node); 450 + return ERR_PTR(res); 451 + } 452 + data = kmap_local_page(page); 536 453 } 537 454 } 538 455 539 456 void hfs_bmap_free(struct hfs_bnode *node) 540 457 { 541 458 struct hfs_btree *tree; 542 - struct page *page; 543 459 u16 off, len; 544 460 u32 nidx; 545 - u8 *data, byte, m; 461 + int res; 546 462 547 463 hfs_dbg("node %u\n", node->this); 548 464 BUG_ON(!node->this); ··· 579 495 } 580 496 len = hfs_brec_lenoff(node, 0, &off); 581 497 } 582 - off += node->page_offset + nidx / 8; 583 - page = node->page[off >> PAGE_SHIFT]; 584 - data = kmap_local_page(page); 585 - off &= ~PAGE_MASK; 586 - m = 1 << (~nidx & 7); 587 - byte = data[off]; 588 - if (!(byte & m)) { 589 - pr_crit("trying to free free bnode " 590 - "%u(%d)\n", 591 - node->this, node->type); 592 - kunmap_local(data); 593 - hfs_bnode_put(node); 594 - return; 498 + 499 + res = hfs_bmap_clear_bit(node, nidx); 500 + if (res == -EINVAL) { 501 + pr_crit("trying to free free bnode %u(%d)\n", 502 + node->this, node->type); 503 + } else if (!res) { 504 + tree->free_nodes++; 505 + mark_inode_dirty(tree->inode); 595 506 } 596 - data[off] = byte & ~m; 597 - set_page_dirty(page); 598 - kunmap_local(data); 507 + 599 508 hfs_bnode_put(node); 600 - tree->free_nodes++; 601 - mark_inode_dirty(tree->inode); 602 509 }
+2
include/linux/hfs_common.h
··· 510 510 #define HFSPLUS_NODE_MXSZ 32768 511 511 #define HFSPLUS_ATTR_TREE_NODE_SIZE 8192 512 512 #define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT 3 513 + #define HFSPLUS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Header node */ 514 + #define HFSPLUS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */ 513 515 #define HFSPLUS_BTREE_HDR_USER_BYTES 128 514 516 515 517 /* btree key type */