[PATCH] f2fs: validate ACL entry sizes before parsing

From: Zhang Cen

Date: Mon May 11 2026 - 03:09:04 EST


f2fs_acl_from_disk() parses disk-provided POSIX ACL xattrs by first
computing an entry count from the total xattr size and then walking the
records according to each entry tag. The walk must prove that the next
short entry header is present before reading e_tag and e_perm, and that a
full entry is present before reading e_id for ACL_USER or ACL_GROUP.

The current code only rejects entry pointers that are strictly past the
end of the xattr buffer. A malformed ACL can make the tag-driven walk
land exactly at the end while the size-derived count still has entries
left. The next iteration then reads e_tag/e_perm one entry past the
supplied ACL value. Sanitized testing on the affected tree reported
slab-out-of-bounds reads in __f2fs_get_acl() from ACL retrieval paths,
with the read address immediately past a 20-byte ACL buffer.

Reject malformed ACLs before each dereference by checking that the short
entry header fits at the start of every iteration, and checking that a
full entry fits before consuming e_id for ACL_USER and ACL_GROUP. This
keeps valid ACLs unchanged while returning -EINVAL for truncated or
width-inconsistent ACL xattr values.

Sanitizer validation reported:
KASAN slab-out-of-bounds in __f2fs_get_acl()
Read of size 2
Call trace:
dump_stack_lvl() (?:?)
print_address_description() (mm/kasan/report.c:373)
__f2fs_get_acl() (fs/f2fs/acl.c:169)
print_report() (?:?)
__virt_addr_valid() (?:?)
srso_alias_return_thunk() (arch/x86/include/asm/nospec-branch.h:375)
kasan_addr_to_slab() (mm/kasan/common.c:45)
kasan_report() (?:?)
f2fs_get_acl() (fs/f2fs/acl.c:200)
__get_acl() (fs/posix_acl.c:114)
vfs_get_acl() (?:?)
do_get_acl() (?:?)
__kvmalloc_node_noprof() (?:?)
do_getxattr() (?:?)
filename_getxattr() (?:?)
strncpy_from_user() (?:?)
path_getxattrat() (fs/xattr.c:838)
rcu_is_watching() (?:?)
do_syscall_64() (arch/x86/entry/syscall_64.c:87)
entry_SYSCALL_64_after_hwframe() (?:?)

Signed-off-by: Zhang Cen <rollkingzzc@xxxxxxxxx>

---
diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c
index fa8d81a30fb9..290fee451637 100644
--- a/fs/f2fs/acl.c
+++ b/fs/f2fs/acl.c
@@ -70,7 +70,7 @@ static struct posix_acl *f2fs_acl_from_disk(const char *value, size_t size)

for (i = 0; i < count; i++) {

- if ((char *)entry > end)
+ if ((char *)entry + sizeof(struct f2fs_acl_entry_short) > end)
goto fail;

acl->a_entries[i].e_tag = le16_to_cpu(entry->e_tag);
@@ -86,6 +86,8 @@ static struct posix_acl *f2fs_acl_from_disk(const char *value, size_t size)
break;

case ACL_USER:
+ if ((char *)entry + sizeof(struct f2fs_acl_entry) > end)
+ goto fail;
acl->a_entries[i].e_uid =
make_kuid(&init_user_ns,
le32_to_cpu(entry->e_id));
@@ -93,6 +95,8 @@ static struct posix_acl *f2fs_acl_from_disk(const char *value, size_t size)
sizeof(struct f2fs_acl_entry));
break;
case ACL_GROUP:
+ if ((char *)entry + sizeof(struct f2fs_acl_entry) > end)
+ goto fail;
acl->a_entries[i].e_gid =
make_kgid(&init_user_ns,
le32_to_cpu(entry->e_id));