[PATCH] hpfs: reject oversized indirect extended attributes
From: 이상호
Date: Wed Jul 01 2026 - 17:50:51 EST
HPFS stores the length of an indirect extended attribute as an unsigned
32-bit on-disk value. hpfs_get_ea() exposes the selected EA length
through an int-sized output parameter, and get_indirect_ea() currently
also receives the length as an int.
That conversion is unsafe for malformed metadata. If a crafted fnode
describes an indirect EA with length 0xffffffff, hpfs_get_ea() assigns
that value to *size as -1 and passes it to get_indirect_ea(). The
allocation then uses size + 1, which wraps to zero, while hpfs_ea_read()
receives the same negative value through an unsigned length parameter and
attempts to copy sector data into the zero-sized allocation.
Keep the indirect EA length unsigned until it has been validated. Reject
values that cannot be represented by the existing int output parameter or
cannot be allocated together with the trailing NUL byte. Also store the
output size only after hpfs_ea_read() succeeds, so callers never observe a
length for data that was not read.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: 이상호 <kudo3228@xxxxxxxxx>
---
fs/hpfs/ea.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/fs/hpfs/ea.c b/fs/hpfs/ea.c
index 4664f9ab06ee..d23de4f9e2f5 100644
--- a/fs/hpfs/ea.c
+++ b/fs/hpfs/ea.c
@@ -7,6 +7,8 @@
* handling extended attributes
*/
+#include <linux/limits.h>
+
#include "hpfs_fn.h"
/* Remove external extended attributes. ano specifies whether a is a
@@ -48,9 +50,14 @@ void hpfs_ea_ext_remove(struct super_block *s, secno a, int ano, unsigned len)
}
}
-static char *get_indirect_ea(struct super_block *s, int ano, secno a, int size)
+static char *get_indirect_ea(struct super_block *s, int ano, secno a,
+ unsigned int size, int *out_size)
{
char *ret;
+ if (size > S32_MAX || (size_t)size + 1 > KMALLOC_MAX_SIZE) {
+ hpfs_error(s, "indirect EA is too large: %u", size);
+ return NULL;
+ }
if (!(ret = kmalloc(size + 1, GFP_NOFS))) {
pr_err("out of memory for EA\n");
return NULL;
@@ -60,6 +67,7 @@ static char *get_indirect_ea(struct super_block *s, int ano, secno a, int size)
return NULL;
}
ret[size] = 0;
+ *out_size = size;
return ret;
}
@@ -138,7 +146,9 @@ char *hpfs_get_ea(struct super_block *s, struct fnode *fnode, char *key, int *si
for (ea = fnode_ea(fnode); ea < ea_end; ea = next_ea(ea))
if (!strcmp(ea->name, key)) {
if (ea_indirect(ea))
- return get_indirect_ea(s, ea_in_anode(ea), ea_sec(ea), *size = ea_len(ea));
+ return get_indirect_ea(s, ea_in_anode(ea),
+ ea_sec(ea), ea_len(ea),
+ size);
if (!(ret = kmalloc((*size = ea_valuelen(ea)) + 1, GFP_NOFS))) {
pr_err("out of memory for EA\n");
return NULL;
@@ -164,7 +174,9 @@ char *hpfs_get_ea(struct super_block *s, struct fnode *fnode, char *key, int *si
return NULL;
if (!strcmp(ea->name, key)) {
if (ea_indirect(ea))
- return get_indirect_ea(s, ea_in_anode(ea), ea_sec(ea), *size = ea_len(ea));
+ return get_indirect_ea(s, ea_in_anode(ea),
+ ea_sec(ea), ea_len(ea),
+ size);
if (!(ret = kmalloc((*size = ea_valuelen(ea)) + 1, GFP_NOFS))) {
pr_err("out of memory for EA\n");
return NULL;
--
2.43.0