Skip to content

Fixed out-of-bounds read of extended attribute values in filestat() - #6429

Open
Dhanu-dynamic wants to merge 1 commit into
cfengine:masterfrom
Dhanu-dynamic:filestat-xattr-nul-terminate
Open

Dhanu-dynamic wants to merge 1 commit into
cfengine:masterfrom
Dhanu-dynamic:filestat-xattr-nul-terminate

Conversation

@Dhanu-dynamic

Copy link
Copy Markdown
Contributor

Repro: as an unprivileged user, setfattr -n user.x -v "$(head -c 4096 /dev/zero | tr '\0' A)" ~/f, then have a root policy evaluate filestat("/home/user/f", "xattr"). Under ASan this is a stack-buffer-overflow read in StringIsPrintable(). A short value such as user.x=abc is followed by stale stack bytes, so it is either dropped as non-printable or returned with stack contents appended.
Cause: lgetxattr() fills data[CF_BUFSIZE] with raw bytes and never NUL-terminates, but FnCallFileStatDetails() then uses data as a C string.
Fix: read at most sizeof(data) - 1 bytes and terminate at datasize. CopyFileExtendedAttributesDisk() in files_copy.c handles values by length and is not affected.

I checked this by running the same loop against real extended attributes under ASan: the over-read before the change, user.x=abc returned correctly after it. The unit test suite still passes. I didn't add a unit test because it would depend on the CI filesystem supporting user.* xattrs.

The "xattr" branch of filestat() reads each extended attribute value
into an uninitialized stack buffer and then uses it as a C string:

    char data[CF_BUFSIZE];
    int datasize = lgetxattr(path, current, data, sizeof(data));
    ...
    if (!StringIsPrintable(data))
    ...
    BufferPrintf(printattr, "%s=%s", current, data);

lgetxattr() returns raw bytes and never adds a terminating NUL. An
unprivileged user can set user.* attributes on files they own, so when
a policy run as root calls filestat() on such a file, a printable value
of exactly 4096 bytes makes StringIsPrintable() read past the end of
data. Shorter values are followed by whatever stale stack bytes come
before the next NUL, which either makes a valid attribute get skipped
as non-printable or copies stack contents into the returned string.

Read at most sizeof(data) - 1 bytes and terminate the value at
datasize. CopyFileExtendedAttributesDisk() in files_copy.c, the other
lgetxattr() caller, handles values by length and is not affected.

Changelog: Title
Signed-off-by: Dhanraj E <dhanraj@labs.digiscrypt.com>
@cf-bottom

Copy link
Copy Markdown

Thank you for submitting a PR! Maybe @craigcomstock can review this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants