CVE-2012-0038: XFS ACL count integer overflow

| Comments

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.

xfs_acl_from_disklink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.

posix_acl_alloc
1
2
3
4
5
6
7
8
9
10
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.

xfs: validate acl countcommit fa8b18ed
1
2
3
4
5
6
7
8
9
10
11
--- 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.

xfs: fix acl count validation in xfs_acl_from_disk()commit 093019cf
1
2
3
4
5
6
7
8
9
10
11
--- 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 3.2 contains only the first patch and remains vulnerable.

Comments