Skip to contentSkip to conversation

hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length

PATCH Superseded Tristan Madani 2 participants 14 messages May 1 – May 7

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

PATCH 1/3 hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length

From: Tristan Madani <[email protected]>

check_and_correct_requested_length() compares (off + len) against node_size using u32 arithmetic. When the caller passes a large len value (e.g. from an underflowed subtraction in hfs_brec_remove()), off + len can wrap past 2^32 and produce a small result, causing the bounds check to pass when it should fail.

For example, with off=14 and len=0xFFFFFFF2 (underflowed from data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6, which is less than a typical node_size of 512, so the check passes and the subsequent memmove reads ~4GB past the node buffer.

Fix this by comparing len against (node_size - off) instead. Since is_bnode_offset_valid() already guarantees off < node_size before this point, the subtraction cannot underflow.

---
 fs/hfs/bnode.c          | 2 +-
 fs/hfsplus/hfsplus_fs.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/hfs/bnode.c +1 -1
index 13d58c51fc46b..c00645a4a5733 100644
@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if (len > node_size - off) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
fs/hfsplus/hfsplus_fs.h +1 -1
index 3545b8dbf11c5..10b2dda3f8044 100644
@@ -600,7 +600,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if (len > node_size - off) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
Signature · 1 line
2.47.3
#1 lore raw

PATCH 2/3 hfs/hfsplus: initialize data buffer in hfs_bnode_read_u16 and hfs_bnode_read_u8

From: Tristan Madani <[email protected]>

hfs_bnode_read_u16() and hfs_bnode_read_u8() declare local data buffers without initialization, then pass them to hfs_bnode_read(). If is_bnode_offset_valid() fails inside hfs_bnode_read(), the function returns early without writing to the buffer, leaving it uninitialized. The caller then returns the garbage value to its caller.

This triggers KMSAN uninit-value reports when a corrupted HFS+ image has a node_size of 1, causing rec_off to underflow in hfs_bnode_find() and the subsequent hfs_bnode_read_u16() to operate on an invalid offset.

Zero-initialize both buffers so that callers get a deterministic zero value when the underlying read fails.

Reported-by [email protected]
Fixes a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
---
 fs/hfs/bnode.c     | 4 ++--
 fs/hfsplus/bnode.c | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
fs/hfs/bnode.c +2 -2
index c00645a4a5733..08307faea7a68 100644
@@ -97,7 +97,7 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 
 u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 {
-	__be16 data;
+	__be16 data = 0;
 	// optimize later...
 	hfs_bnode_read(node, &data, off, 2);
 	return be16_to_cpu(data);
@@ -105,7 +105,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 
 u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
 {
-	u8 data;
+	u8 data = 0;
 	// optimize later...
 	hfs_bnode_read(node, &data, off, 1);
 	return data;
fs/hfsplus/bnode.c +2 -2
index f8b5a8ae58ff5..35790085b5b2e 100644
@@ -55,7 +55,7 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 
 u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 {
-	__be16 data;
+	__be16 data = 0;
 	/* TODO: optimize later... */
 	hfs_bnode_read(node, &data, off, 2);
 	return be16_to_cpu(data);
@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 
 u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
 {
-	u8 data;
+	u8 data = 0;
 	/* TODO: optimize later... */
 	hfs_bnode_read(node, &data, off, 1);
 	return data;
Signature · 1 line
2.47.3
#2 lore raw

Re: [PATCH 2/3] hfs/hfsplus: initialize data buffer in hfs_bnode_read_u16 and hfs_bnode_read_u8

Quoted 62 lines · Tristan Madani

From: Tristan Madani <[email protected]>

hfs_bnode_read_u16() and hfs_bnode_read_u8() declare local data buffers without initialization, then pass them to hfs_bnode_read(). If is_bnode_offset_valid() fails inside hfs_bnode_read(), the function returns early without writing to the buffer, leaving it uninitialized. The caller then returns the garbage value to its caller.

This triggers KMSAN uninit-value reports when a corrupted HFS+ image has a node_size of 1, causing rec_off to underflow in hfs_bnode_find() and the subsequent hfs_bnode_read_u16() to operate on an invalid offset.

Zero-initialize both buffers so that callers get a deterministic zero value when the underlying read fails.

Reported-by [email protected]
Fixes a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
---
 fs/hfs/bnode.c     | 4 ++--
 fs/hfsplus/bnode.c | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
fs/hfs/bnode.c +2 -2
index c00645a4a5733..08307faea7a68 100644
@@ -97,7 +97,7 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 
 u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 {
-	__be16 data;
+	__be16 data = 0;
 	// optimize later...
 	hfs_bnode_read(node, &data, off, 2);
 	return be16_to_cpu(data);
@@ -105,7 +105,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 
 u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
 {
-	u8 data;
+	u8 data = 0;
 	// optimize later...
 	hfs_bnode_read(node, &data, off, 1);
 	return data;
fs/hfsplus/bnode.c +1 -1
index f8b5a8ae58ff5..35790085b5b2e 100644
@@ -55,7 +55,7 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 
 u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 {
-	__be16 data;
+	__be16 data = 0;
 	/* TODO: optimize later... */
 	hfs_bnode_read(node, &data, off, 2);
 	return be16_to_cpu(data);

fs/hfsplus/bnode.c u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off) · line 69

Quoted 8 lines
fs/hfsplus/bnode.c +1 -1
@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
 
 u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
 {
-	u8 data;
+	u8 data = 0;
 	/* TODO: optimize later... */
 	hfs_bnode_read(node, &data, off, 1);
 	return data;

If I remember correctly, I already shared that hfs_bnode_read() is called in multiple places. So, this patch is not enough for complete fix of the issue.

Thanks,
Slava.

#3 lore raw

PATCH 3/3 hfsplus: fix null pointer dereference in hfsplus_create_attributes_file

In reply to Tristan Madani

From: Tristan Madani <[email protected]>

hfsplus_create_attributes_file() calls hfsplus_mark_inode_dirty() with HFSPLUS_ATTR_TREE_I(sb) before sbi->attr_tree has been set by hfs_btree_open(). HFSPLUS_ATTR_TREE_I dereferences sbi->attr_tree to reach ->inode, causing a null pointer dereference when attr_tree is still NULL.

Move the mark_dirty call to after hfs_btree_open() and guard it with a NULL check on sbi->attr_tree.

Reported-by [email protected]
Fixes ee8422d00b7c ("hfsplus: fix potential Allocation File corruption after fsync")
---
 fs/hfsplus/xattr.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/hfsplus/xattr.c +2 -1
index 452a1f9becb2d..1ea9f313368c5 100644
@@ -317,12 +317,13 @@ static int hfsplus_create_attributes_file(struct super_block *sb)
 		next_node++;
 	}
 
-	hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
 	hfsplus_mark_inode_dirty(attr_file, HFSPLUS_I_ATTR_DIRTY);
 
 	sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID);
 	if (!sbi->attr_tree)
 		pr_err("failed to load attributes file\n");
+	else
+		hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
 
 failed_header_node_init:
 	kfree(buf);
Signature · 1 line
2.47.3
#4 lore raw

Re: [PATCH 3/3] hfsplus: fix null pointer dereference in hfsplus_create_attributes_file

Quoted 30 lines · Tristan Madani

From: Tristan Madani <[email protected]>

hfsplus_create_attributes_file() calls hfsplus_mark_inode_dirty() with HFSPLUS_ATTR_TREE_I(sb) before sbi->attr_tree has been set by hfs_btree_open(). HFSPLUS_ATTR_TREE_I dereferences sbi->attr_tree to reach ->inode, causing a null pointer dereference when attr_tree is still NULL.

Move the mark_dirty call to after hfs_btree_open() and guard it with a NULL check on sbi->attr_tree.

Reported-by [email protected]
Fixes ee8422d00b7c ("hfsplus: fix potential Allocation File corruption after fsync")
---
 fs/hfsplus/xattr.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/hfsplus/xattr.c +0 -1
index 452a1f9becb2d..1ea9f313368c5 100644
@@ -317,12 +317,13 @@ static int hfsplus_create_attributes_file(struct super_block *sb)
 		next_node++;
 	}
 
-	hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);

fs/hfsplus/xattr.c static int hfsplus_create_attributes_file(struct super_block *sb) · line 329

Quoted 10 lines
fs/hfsplus/xattr.c +2 -0
 	hfsplus_mark_inode_dirty(attr_file, HFSPLUS_I_ATTR_DIRTY);
 
 	sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID);
 	if (!sbi->attr_tree)
 		pr_err("failed to load attributes file\n");
+	else
+		hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
 
 failed_header_node_init:
 	kfree(buf);

This patch already fixes the issue:

https://lore.kernel.org/linux-fsdevel/[email protected]/

Thanks,
Slava.

#5 lore raw
In reply to Tristan Madani
Quoted 36 lines · Tristan Madani

From: Tristan Madani <[email protected]>

check_and_correct_requested_length() compares (off + len) against node_size using u32 arithmetic. When the caller passes a large len value (e.g. from an underflowed subtraction in hfs_brec_remove()), off + len can wrap past 2^32 and produce a small result, causing the bounds check to pass when it should fail.

For example, with off=14 and len=0xFFFFFFF2 (underflowed from data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6, which is less than a typical node_size of 512, so the check passes and the subsequent memmove reads ~4GB past the node buffer.

Fix this by comparing len against (node_size - off) instead. Since is_bnode_offset_valid() already guarantees off < node_size before this point, the subtraction cannot underflow.

---
 fs/hfs/bnode.c          | 2 +-
 fs/hfsplus/hfsplus_fs.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/hfs/bnode.c +0 -0
index 13d58c51fc46b..c00645a4a5733 100644

fs/hfs/bnode.c u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) · line 44

Quoted 5 lines
fs/hfs/bnode.c +1 -1
@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if (len > node_size - off) {

I don't agree with likewise change. Probably, we need to have:

(u64)off + len

Thanks,
Slava.

Quoted 16 lines
fs/hfs/bnode.c +0 -0
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
fs/hfsplus/hfsplus_fs.h +1 -1
index 3545b8dbf11c5..10b2dda3f8044 100644
@@ -600,7 +600,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if (len > node_size - off) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
#6 lore raw

v2 cover hfs/hfsplus: fix OOB access and uninit-value in bnode operations

In reply to Tristan Madani

From: Tristan Madani <[email protected]>

Two fixes for long-standing syzbot reports in the HFS/HFS+ B-tree node handling code.

Changes since v1 (20260501110218.29906-{1..3}[email protected]):
- Patch 1: use (u64) widening cast per Dubeyko's feedback
- Patch 2: memset inside hfs_bnode_read() covering all callers in both fs/hfs/ and fs/hfsplus/, per Dubeyko's feedback
- Dropped patch 3 (NULL deref in hfsplus_create_attributes_file):
already fixed by Dubeyko's patch [1]

[1] https://lore.kernel.org/linux-fsdevel/[email protected]/

Tristan Madani (2):
hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length hfs/hfsplus: zero-initialize buffer in hfs_bnode_read

 fs/hfs/bnode.c          | 4 +++-
 fs/hfsplus/bnode.c      | 2 ++
 fs/hfsplus/hfsplus_fs.h | 2 +-
 3 files changed, 6 insertions(+), 2 deletions(-)
Signature · 1 line
2.47.3
#7 lore raw

PATCH v2 1/2 hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length

From: Tristan Madani <[email protected]>

check_and_correct_requested_length() compares (off + len) against node_size using u32 arithmetic. When the caller passes a large len value (e.g. from an underflowed subtraction in hfs_brec_remove()), off + len can wrap past 2^32 and produce a small result, causing the bounds check to pass when it should fail.

For example, with off=14 and len=0xFFFFFFF2 (underflowed from data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6, which is less than a typical node_size of 512, so the check passes and the subsequent memmove reads ~4GB past the node buffer.

Fix this by widening the addition to u64 before comparing against node_size. This prevents the u32 wrap while keeping the logic straightforward.

---
 fs/hfs/bnode.c          | 2 +-
 fs/hfsplus/hfsplus_fs.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/hfs/bnode.c +1 -1
index 13d58c51fc46b..9571f33b91085 100644
@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if ((u64)off + len > node_size) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
fs/hfsplus/hfsplus_fs.h +1 -1
index 3545b8dbf11c5..0e4268de9e60e 100644
@@ -600,7 +600,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if ((u64)off + len > node_size) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
Signature · 1 line
2.47.3
Reviewed-by on v2 1/2
#8 lore raw
Quoted 49 lines · Tristan Madani

From: Tristan Madani <[email protected]>

check_and_correct_requested_length() compares (off + len) against node_size using u32 arithmetic. When the caller passes a large len value (e.g. from an underflowed subtraction in hfs_brec_remove()), off + len can wrap past 2^32 and produce a small result, causing the bounds check to pass when it should fail.

For example, with off=14 and len=0xFFFFFFF2 (underflowed from data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6, which is less than a typical node_size of 512, so the check passes and the subsequent memmove reads ~4GB past the node buffer.

Fix this by widening the addition to u64 before comparing against node_size. This prevents the u32 wrap while keeping the logic straightforward.

---
 fs/hfs/bnode.c          | 2 +-
 fs/hfsplus/hfsplus_fs.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/hfs/bnode.c +1 -1
index 13d58c51fc46b..9571f33b91085 100644
@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if ((u64)off + len > node_size) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "
fs/hfsplus/hfsplus_fs.h +0 -0
index 3545b8dbf11c5..0e4268de9e60e 100644

fs/hfsplus/hfsplus_fs.h u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) · line 606

Quoted 8 lines
fs/hfsplus/hfsplus_fs.h +1 -1
@@ -600,7 +600,7 @@ u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
 
 	node_size = node->tree->node_size;
 
-	if ((off + len) > node_size) {
+	if ((u64)off + len > node_size) {
 		u32 new_len = node_size - off;
 
 		pr_err("requested length has been corrected: "

Looks good. Thanks for the fix.

Thanks,
Slava.

#9 lore raw

PATCH v2 2/2 hfs/hfsplus: zero-initialize buffer in hfs_bnode_read

In reply to Tristan Madani

From: Tristan Madani <[email protected]>

hfs_bnode_read() can return early without writing to the output buffer when is_bnode_offset_valid() fails or when check_and_correct_requested_ length() corrects the length to zero. Callers such as hfs_bnode_read_ u16() and hfs_bnode_read_u8() pass stack-allocated buffers and use the result unconditionally, leading to KMSAN uninit-value reports.

Rather than initializing at each individual call site, zero the buffer at the start of hfs_bnode_read() before any validation checks. This ensures all callers in both hfs and hfsplus get a deterministic zero value regardless of which early-return path is taken.

Reported-by [email protected]
Fixes a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
---
 fs/hfs/bnode.c     | 2 ++
 fs/hfsplus/bnode.c | 2 ++
 2 files changed, 4 insertions(+)
fs/hfs/bnode.c +2 -0
index 9571f33b91085..25cef62fbba6d 100644
@@ -64,6 +64,8 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 	u32 bytes_read;
 	u32 bytes_to_read;
 
+	memset(buf, 0, len);
+
 	if (!is_bnode_offset_valid(node, off))
 		return;
 
fs/hfsplus/bnode.c +2 -0
index f8b5a8ae58ff5..14d1af2c7ba93 100644
@@ -25,6 +25,8 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 	struct page **pagep;
 	u32 l;
 
+	memset(buf, 0, len);
+
 	if (!is_bnode_offset_valid(node, off))
 		return;
 
Signature · 1 line
2.47.3
#10 lore raw

Re: [PATCH v2 2/2] hfs/hfsplus: zero-initialize buffer in hfs_bnode_read

Quoted 42 lines · Tristan Madani

From: Tristan Madani <[email protected]>

hfs_bnode_read() can return early without writing to the output buffer when is_bnode_offset_valid() fails or when check_and_correct_requested_ length() corrects the length to zero. Callers such as hfs_bnode_read_ u16() and hfs_bnode_read_u8() pass stack-allocated buffers and use the result unconditionally, leading to KMSAN uninit-value reports.

Rather than initializing at each individual call site, zero the buffer at the start of hfs_bnode_read() before any validation checks. This ensures all callers in both hfs and hfsplus get a deterministic zero value regardless of which early-return path is taken.

Reported-by [email protected]
Fixes a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
---
 fs/hfs/bnode.c     | 2 ++
 fs/hfsplus/bnode.c | 2 ++
 2 files changed, 4 insertions(+)
fs/hfs/bnode.c +2 -0
index 9571f33b91085..25cef62fbba6d 100644
@@ -64,6 +64,8 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 	u32 bytes_read;
 	u32 bytes_to_read;
 
+	memset(buf, 0, len);
+
 	if (!is_bnode_offset_valid(node, off))
 		return;
 
fs/hfsplus/bnode.c +0 -0
index f8b5a8ae58ff5..14d1af2c7ba93 100644

fs/hfsplus/bnode.c void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) · line 32

Quoted 8 lines
fs/hfsplus/bnode.c +2 -0
@@ -25,6 +25,8 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 	struct page **pagep;
 	u32 l;
 
+	memset(buf, 0, len);
+
 	if (!is_bnode_offset_valid(node, off))
 		return;
 

We are returning back to the same my question. What if the caller of hfs_bnode_read() provides the len == 0 somehow but the buffer has not zero size on the caller side? I assume that memset() will do nothing and the buffer still be not initialized. Am I correct here?

Thanks,
Slava.

#11 lore raw

Re: [PATCH v2 2/2] hfs/hfsplus: zero-initialize buffer in hfs_bnode_read

Quoted 4 lines · Viacheslav Dubeyko

We are returning back to the same my question. What if the caller of hfs_bnode_read() provides the len == 0 somehow but the buffer has not zero size on the caller side? I assume that memset() will do nothing and the buffer still be not initialized. Am I correct here?

You are correct that memset(buf, 0, 0) is a no-op. But if the caller passes len == 0, it is asking to read zero bytes -- it should not expect the function to initialize anything beyond that.

The bug this fixes is different: callers pass a non-zero len (e.g. hfs_bnode_read_u16 passes 2, hfs_bnode_read_u8 passes 1), but check_and_correct_requested_length() internally reduces the effective read to 0 due to a corrupted image. The memset runs with the original caller-supplied len before any internal validation, so the buffer is already zeroed by the time the early-return happens.

No current caller passes len == 0 -- hfs_bnode_read_u16() always passes 2, hfs_bnode_read_u8() always passes 1, and hfs_bnode_read_key() passes a key_len that is validated against the btree key size.

Thanks,
Tristan

#12 lore raw

Re: [PATCH v2 2/2] hfs/hfsplus: zero-initialize buffer in hfs_bnode_read

Quoted 17 lines · Tristan Madani
Quoted 4 lines · Viacheslav Dubeyko

We are returning back to the same my question. What if the caller of hfs_bnode_read() provides the len == 0 somehow but the buffer has not zero size on the caller side? I assume that memset() will do nothing and the buffer still be not initialized. Am I correct here?

You are correct that memset(buf, 0, 0) is a no-op. But if the caller passes len == 0, it is asking to read zero bytes -- it should not expect the function to initialize anything beyond that.

The bug this fixes is different: callers pass a non-zero len (e.g. hfs_bnode_read_u16 passes 2, hfs_bnode_read_u8 passes 1), but check_and_correct_requested_length() internally reduces the effective read to 0 due to a corrupted image. The memset runs with the original caller-supplied len before any internal validation, so the buffer is already zeroed by the time the early-return happens.

Quoted 4 lines

No current caller passes len == 0 -- hfs_bnode_read_u16() always passes 2, hfs_bnode_read_u8() always passes 1, and hfs_bnode_read_key() passes a key_len that is validated against the btree key size.

OK. I think I can agree with you. If we have len == 0 as input argument, then we have:

if (len == 0) {
<skipped>
return;
}

And if check_and_correct_requested_length() corrects the len to 0:

len = check_and_correct_requested_length(node, off, len);

then loop will do nothing because of len == 0:

for (bytes_read = 0; bytes_read < len; bytes_read += bytes_to_read) { <skipped>
}

Thanks,
Slava.

Reviewed-by on v2 2/2
#13 lore raw

Re: [PATCH v2 2/2] hfs/hfsplus: zero-initialize buffer in hfs_bnode_read

In reply to Tristan Madani
Quoted 43 lines · Tristan Madani

From: Tristan Madani <[email protected]>

hfs_bnode_read() can return early without writing to the output buffer when is_bnode_offset_valid() fails or when check_and_correct_requested_ length() corrects the length to zero. Callers such as hfs_bnode_read_ u16() and hfs_bnode_read_u8() pass stack-allocated buffers and use the result unconditionally, leading to KMSAN uninit-value reports.

Rather than initializing at each individual call site, zero the buffer at the start of hfs_bnode_read() before any validation checks. This ensures all callers in both hfs and hfsplus get a deterministic zero value regardless of which early-return path is taken.

Reported-by [email protected]
Fixes a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
---
 fs/hfs/bnode.c     | 2 ++
 fs/hfsplus/bnode.c | 2 ++
 2 files changed, 4 insertions(+)
fs/hfs/bnode.c +2 -0
index 9571f33b91085..25cef62fbba6d 100644
@@ -64,6 +64,8 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 	u32 bytes_read;
 	u32 bytes_to_read;
 
+	memset(buf, 0, len);
+
 	if (!is_bnode_offset_valid(node, off))
 		return;
 
fs/hfsplus/bnode.c +0 -0
index f8b5a8ae58ff5..14d1af2c7ba93 100644

fs/hfsplus/bnode.c void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) · line 32

Quoted 8 lines
fs/hfsplus/bnode.c +2 -0
@@ -25,6 +25,8 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
 	struct page **pagep;
 	u32 l;
 
+	memset(buf, 0, len);
+
 	if (!is_bnode_offset_valid(node, off))
 		return;
 

Looks good. Thanks a lot for the fix.

Thanks,
Slava.