From nobody Thu Apr 2 17:10:30 2026 Received: from mail-pf1-f179.google.com (mail-pf1-f179.google.com [209.85.210.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A44039A807 for ; Thu, 26 Feb 2026 09:12:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772097174; cv=none; b=EOO/cKsKPi+OZA9oh9UimH9a+S+07hA0H9z9usjIPrnNX4KljXDWqHxy1EVjSnwdQAgRGK1UJ0iwY1bimuDg2KTYF7guCXrWo8Kq5YGmPzRl3qJYxWtQOyZWRJUndfXXzGVbuPztBEJBOArDcfrBwUEaAl6pVJKgWn/saWBgG8Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772097174; c=relaxed/simple; bh=o5IFg1Vyd66vcwz3s0bwn9rLJf9YTJGFLGnQA6pztpk=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=obifAFHrFE4nJvM+SVOlD4FC8zV9jpbgahyGae2Uo5mgNZJFjySBznAx1c4WhjRR9Sl1k0EeL5V4vyxIvLlHlMgNDmHzKVUiEoShCLiobLLn1T7ONGcMbtXXun+xtftGBE0NhZu9NJ9yIUr8ZPVZlvT9Hc/BMUdv+MND1EqW5sk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=J+9noE2q; arc=none smtp.client-ip=209.85.210.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="J+9noE2q" Received: by mail-pf1-f179.google.com with SMTP id d2e1a72fcca58-8230c2d3128so331076b3a.0 for ; Thu, 26 Feb 2026 01:12:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772097172; x=1772701972; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=PGiXuIQEbuf9BXwhueXtToZeIPVu9UDxY4vWexcOij4=; b=J+9noE2qxshpd0mzA8OozGNx1iF4Tn0Sht+0XbRHisp8QqJRpFf5aHRu6RQziqqHfo Te5lA7xeyPMaDww1dZa8/rLX5zx9XMSfERwxNMMS+PicHq09UygrUy8fVAtyJN6iKLkb qBxZis7CVBULdp1N44vsMhpjru+GALjSe/6wt9kefBPgkQLugYE4PZ/c6qq0fcU0jiIv QzKjxxsusv9uqNWpN9YJM1qIPVyM267KexU8NW+rCce2vtYBrqJCQH9f1Owa/1XuxW9H nSr4mBG+V5Ai7NzfxaLKFAUxOOguaxoC+EiHkp5L839k9RVyvyjd+dRXDmy3pqK8Kna3 jNfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772097172; x=1772701972; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=PGiXuIQEbuf9BXwhueXtToZeIPVu9UDxY4vWexcOij4=; b=MoyS0fL2lDq2Oo5+ZRObiC+ilKl5DZ86qi3/REDfkJpsTdGfUirdYlAIBJTazy82Nw wZ5KaXOMN8w/61k9Pg+lnRREHSkMm947HWKQeGzLWIM2XNuDmB0HmEil7QdCgXwH93Yy /kJPtYZI5k8YLuGi5FTxJtx3sWR9ipka7EAwmVnhqRNHxqZn3lCDezS7gt/pSRUhMReG G/rtQgl9+8lL86XRZ1781fu+46bh2n8s6usiJCmgiuOJ8F5uy0AeQxpy1mlDy1sSHKyq KXi43RUWPi6Rnq0cWX1v7noD66QQX3eryYctWzF7ihkkQLEMKihRJYyj50GmSCC5balu Y0QQ== X-Forwarded-Encrypted: i=1; AJvYcCVxHw+S9nE9OsDZPlQfza2xyWAD6Z41qc9NEODJjG6/BeE4mc5Gdrw5LkQM3R2KpExxGZfOtuwMh7a3A4Y=@vger.kernel.org X-Gm-Message-State: AOJu0Yz6gTOR3mjmQJ1wi6IKU86S2rDob63MUM7ery7BSZMxm9OzXBgv aDidhiCwq8StwHl/P/3ZOP/fK+kMRxgsS3d6/6LzSECIW8BGmUiEujnC X-Gm-Gg: ATEYQzzPfewwzqQHRMiVM9WZsfgumKqYIQYAye6yBUVkzUrJ+GmWeU8BMQPOf2FphG/ YvOT1SRt16+R0BmwyYpeCM+Nv88bKv8spVXwu39Ox5oICTqBK/kF4zy3tNhmizkG9qbwKOcsg2Y igbShzomnzteNigcX8+XbjvygEe5RXvIIzwfA7z4cpbP5cGdcu/GVjcqWVDaxgTAe0ExBTpXNpc k8136DCWDcSV3m3Pi8Cvctt3Kc1o1R4E0xrEQxeiTdE1jF2t5prLjFISlOzlTSf9SdTcTn9iPre Z82ld9Mm67OuV7Q55kHvQ3g0IVCeMde3fr1S3VEFvzQ7KBsMKTZQ1KwRAuRJsO47d/INFNYFRJk +vigB861iD6Ipax1vm0hvM1u6w1pVXn2/RgOwaiBUq1g2CN/UMzWKm6ux/i0hEqVM+R5310qVaN WiPBf1+94wYHds/+SUJDxtU9Jp0a77zbS5vWIeKzm1xp5mueg= X-Received: by 2002:a05:6a21:6e8a:b0:38d:f745:4d5f with SMTP id adf61e73a8af0-395b47b4e73mr1905944637.24.1772097171578; Thu, 26 Feb 2026 01:12:51 -0800 (PST) Received: from localhost.localdomain ([223.185.37.137]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-c70fa5e4aafsm1457484a12.4.2026.02.26.01.12.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 01:12:51 -0800 (PST) From: Shardul Bankar X-Google-Original-From: Shardul Bankar To: slava@dubeyko.com, glaubitz@physik.fu-berlin.de, frank.li@vivo.com, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: janak@mpiricsoftware.com, janak@mpiric.us, shardulsb08@gmail.com, Shardul Bankar Subject: [PATCH v4 1/2] hfsplus: refactor b-tree map page access and add node-type validation Date: Thu, 26 Feb 2026 14:42:34 +0530 Message-Id: <20260226091235.927749-2-shardul.b@mpiricsoftware.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260226091235.927749-1-shardul.b@mpiricsoftware.com> References: <20260226091235.927749-1-shardul.b@mpiricsoftware.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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() to duplicate boilerplate code to validate offsets, correct lengths, and map the underlying pages via kmap_local_page() for both the initial header node and the subsequent map nodes in the chain. Introduce a generic helper, hfs_bmap_get_map_page(), to encapsulate the map record access. This helper: 1. Automatically validates the node->type against HFS_NODE_HEADER and HFS_NODE_MAP to prevent misinterpreting corrupted nodes. 2. Infers the correct record index (2 or 0) based on the node type. 3. Handles the offset calculation, length validation, and page mapping. Refactor hfs_bmap_alloc() to utilize this helper, stripping out the redundant setup blocks. As part of this cleanup, the double pointer iterator (struct page **pagep) is replaced with a simpler unsigned int index (page_idx) for cleaner page boundary crossing. This deduplicates the allocator logic, hardens the map traversal against fuzzed/corrupted images, and provides a generic map-access abstraction that will be utilized by upcoming mount-time validation checks. Signed-off-by: Shardul Bankar --- fs/hfsplus/btree.c | 78 +++++++++++++++++++++++++++----------- include/linux/hfs_common.h | 3 ++ 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index 1220a2f22737..22efd6517ef4 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -129,6 +129,47 @@ u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 = node_size, return clump_size; } =20 +/* + * Maps the page containing the b-tree map record and calculates offsets. + * Automatically handles the difference between header and map nodes. + * Returns the mapped data pointer, or an ERR_PTR on failure. + * Note: The caller is responsible for calling kunmap_local(data). + */ +static u8 *hfs_bmap_get_map_page(struct hfs_bnode *node, u16 *off, u16 *le= n, + unsigned int *page_idx) +{ + u16 rec_idx, off16; + + if (node->this =3D=3D HFSPLUS_TREE_HEAD) { + if (node->type !=3D HFS_NODE_HEADER) { + pr_err("hfsplus: invalid btree header node\n"); + return ERR_PTR(-EIO); + } + rec_idx =3D HFSPLUS_BTREE_HDR_MAP_REC_INDEX; + } else { + if (node->type !=3D HFS_NODE_MAP) { + pr_err("hfsplus: invalid btree map node\n"); + return ERR_PTR(-EIO); + } + rec_idx =3D HFSPLUS_BTREE_MAP_NODE_REC_INDEX; + } + + *len =3D hfs_brec_lenoff(node, rec_idx, &off16); + if (!*len) + return ERR_PTR(-ENOENT); + + if (!is_bnode_offset_valid(node, off16)) + return ERR_PTR(-EIO); + + *len =3D check_and_correct_requested_length(node, off16, *len); + + off16 +=3D node->page_offset; + *page_idx =3D off16 >> PAGE_SHIFT; + *off =3D off16 & ~PAGE_MASK; + + return kmap_local_page(node->page[*page_idx]); +} + /* Get a reference to a B*Tree and do some initial checks */ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id) { @@ -374,10 +415,9 @@ int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_= nodes) struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) { struct hfs_bnode *node, *next_node; - struct page **pagep; + unsigned int page_idx; u32 nidx, idx; - unsigned off; - u16 off16; + u16 off; u16 len; u8 *data, byte, m; int i, res; @@ -390,30 +430,24 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tr= ee) node =3D hfs_bnode_find(tree, nidx); if (IS_ERR(node)) return node; - len =3D hfs_brec_lenoff(node, 2, &off16); - off =3D off16; - - if (!is_bnode_offset_valid(node, off)) { + data =3D hfs_bmap_get_map_page(node, &off, &len, &page_idx); + if (IS_ERR(data)) { + res =3D PTR_ERR(data); hfs_bnode_put(node); - return ERR_PTR(-EIO); + return ERR_PTR(res); } - len =3D check_and_correct_requested_length(node, off, len); =20 - off +=3D node->page_offset; - pagep =3D node->page + (off >> PAGE_SHIFT); - data =3D kmap_local_page(*pagep); - off &=3D ~PAGE_MASK; idx =3D 0; =20 for (;;) { while (len) { byte =3D data[off]; if (byte !=3D 0xff) { - for (m =3D 0x80, i =3D 0; i < 8; m >>=3D 1, i++) { + for (m =3D HFSPLUS_BTREE_NODE0_BIT, i =3D 0; i < 8; m >>=3D 1, i++) { if (!(byte & m)) { idx +=3D i; data[off] |=3D m; - set_page_dirty(*pagep); + set_page_dirty(node->page[page_idx]); kunmap_local(data); tree->free_nodes--; mark_inode_dirty(tree->inode); @@ -425,7 +459,7 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree) } if (++off >=3D PAGE_SIZE) { kunmap_local(data); - data =3D kmap_local_page(*++pagep); + data =3D kmap_local_page(node->page[++page_idx]); off =3D 0; } idx +=3D 8; @@ -443,12 +477,12 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tr= ee) return next_node; node =3D next_node; =20 - len =3D hfs_brec_lenoff(node, 0, &off16); - off =3D off16; - off +=3D node->page_offset; - pagep =3D node->page + (off >> PAGE_SHIFT); - data =3D kmap_local_page(*pagep); - off &=3D ~PAGE_MASK; + data =3D hfs_bmap_get_map_page(node, &off, &len, &page_idx); + if (IS_ERR(data)) { + res =3D PTR_ERR(data); + hfs_bnode_put(node); + return ERR_PTR(res); + } } } =20 diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h index dadb5e0aa8a3..8238f55dd1d3 100644 --- a/include/linux/hfs_common.h +++ b/include/linux/hfs_common.h @@ -510,7 +510,10 @@ struct hfs_btree_header_rec { #define HFSPLUS_NODE_MXSZ 32768 #define HFSPLUS_ATTR_TREE_NODE_SIZE 8192 #define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT 3 +#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Heade= r node */ +#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */ #define HFSPLUS_BTREE_HDR_USER_BYTES 128 +#define HFSPLUS_BTREE_NODE0_BIT (1 << 7) =20 /* btree key type */ #define HFSPLUS_KEY_CASEFOLDING 0xCF /* case-insensitive */ --=20 2.34.1