CVE-2012-0038: XFS ACL count integer overflow
This is a classical integer overflow in the Linux kernel’s XFS filesystem, which could further lead to a buffer overflow. To exploit it, an attacker needs to craft a corrupted XFS, for example, via a USB flash drive or remotely mounted filesystem.
The vulnerability seems to be introduced in commit ef14f0c1 in July 2009.
STATIC struct posix_acl *
xfs_acl_from_disk(struct xfs_acl *aclp)
{
struct posix_acl_entry *acl_e;
struct posix_acl *acl;
struct xfs_acl_entry *ace;
int count, i;
count = be32_to_cpu(aclp->acl_cnt);
acl = posix_acl_alloc(count, GFP_KERNEL);
if (!acl)
return ERR_PTR(-ENOMEM);
for (i = 0; i < count; i++) {
acl_e = &acl->a_entries[i];
ace = &aclp->acl_entry[i];
...
acl_e->e_tag = be32_to_cpu(ace->ae_tag);
acl_e->e_perm = be16_to_cpu(ace->ae_perm);
...
}
return acl;
...
}
Note that count
is passed into posix_acl_alloc()
without any
validation; it is converted from aclp->acl_cnt
, which is directly
read from disk.
struct posix_acl *
posix_acl_alloc(int count, gfp_t flags)
{
const size_t size = sizeof(struct posix_acl) +
count * sizeof(struct posix_acl_entry);
struct posix_acl *acl = kmalloc(size, flags);
if (acl)
posix_acl_init(acl, count);
return acl;
}
Given a large count
, the allocation size for kmalloc()
would
wrap to become a small integer. Back to xfs_acl_from_disk()
,
this would cause out-of-bounds write in the subsequent loop.
Later I realized that there’s a recent commit in XFS’s git repository that tried to fix the problem.
--- a/fs/xfs/xfs_acl.c
+++ b/fs/xfs/xfs_acl.c
@@ -42,6 +42,8 @@ xfs_acl_from_disk(struct xfs_acl *aclp)
int count, i;
count = be32_to_cpu(aclp->acl_cnt);
+ if (count > XFS_ACL_MAX_ENTRIES)
+ return ERR_PTR(-EFSCORRUPTED);
acl = posix_acl_alloc(count, GFP_KERNEL);
if (!acl)
However the patch doesn’t really work: count
is a signed integer;
an attacker could feed in a negative value and bypass the sanity
check. So here goes my follow-up patch.
--- a/fs/xfs/xfs_acl.c
+++ b/fs/xfs/xfs_acl.c
@@ -39,7 +39,7 @@ xfs_acl_from_disk(struct xfs_acl *aclp)
struct posix_acl_entry *acl_e;
struct posix_acl *acl;
struct xfs_acl_entry *ace;
- int count, i;
+ unsigned int count, i;
count = be32_to_cpu(aclp->acl_cnt);
if (count > XFS_ACL_MAX_ENTRIES)
BTW, Linux kernel 3.2 contains only the first patch and remains vulnerable.