diff mbox series

fuse: use unsigned type for getxattr/listxattr size truncation

Message ID 20240819-fuse-oob-error-fix-v1-1-9af04eeb4833@google.com (mailing list archive)
State New
Headers show
Series fuse: use unsigned type for getxattr/listxattr size truncation | expand

Commit Message

Jann Horn Aug. 19, 2024, 5:52 p.m. UTC
The existing code uses min_t(ssize_t, outarg.size, XATTR_LIST_MAX) when
parsing the FUSE daemon's response to a zero-length getxattr/listxattr
request.
On 32-bit kernels, where ssize_t and outarg.size are the same size, this is
wrong: The min_t() will pass through any size values that are negative when
interpreted as signed.
fuse_listxattr() will then return this userspace-supplied negative value,
which callers will treat as an error value.


This kind of bug pattern can lead to fairly bad security bugs because of
how error codes are used in the Linux kernel. If a caller were to convert
the numeric error into an error pointer, like so:

    struct foo *func(...) {
      int len = fuse_getxattr(..., NULL, 0);
      if (len < 0)
        return ERR_PTR(len);
      ...
    }

then it would end up returning this userspace-supplied negative value cast
to a pointer - but the caller of this function wouldn't recognize it as an
error pointer (IS_ERR_VALUE() only detects values in the narrow range in
which legitimate errno values are), and so it would just be treated as a
kernel pointer.

I think there is at least one theoretical codepath where this could happen,
but that path would involve virtio-fs with submounts plus some weird
SELinux configuration, so I think it's probably not a concern in practice.

Cc: stable@vger.kernel.org
Fixes: 63401ccdb2ca ("fuse: limit xattr returned size")
Signed-off-by: Jann Horn <jannh@google.com>
---
 fs/fuse/xattr.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)


---
base-commit: b0da640826ba3b6506b4996a6b23a429235e6923
change-id: 20240819-fuse-oob-error-fix-664d082176d5

Comments

Miklos Szeredi Aug. 21, 2024, 4:33 p.m. UTC | #1
On Mon, 19 Aug 2024 at 19:52, Jann Horn <jannh@google.com> wrote:
>
> The existing code uses min_t(ssize_t, outarg.size, XATTR_LIST_MAX) when
> parsing the FUSE daemon's response to a zero-length getxattr/listxattr
> request.
> On 32-bit kernels, where ssize_t and outarg.size are the same size, this is
> wrong: The min_t() will pass through any size values that are negative when
> interpreted as signed.
> fuse_listxattr() will then return this userspace-supplied negative value,
> which callers will treat as an error value.
>

Applied, thanks.

Miklos
diff mbox series

Patch

diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c
index 5b423fdbb13f..9f568d345c51 100644
--- a/fs/fuse/xattr.c
+++ b/fs/fuse/xattr.c
@@ -81,7 +81,7 @@  ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value,
 	}
 	ret = fuse_simple_request(fm, &args);
 	if (!ret && !size)
-		ret = min_t(ssize_t, outarg.size, XATTR_SIZE_MAX);
+		ret = min_t(size_t, outarg.size, XATTR_SIZE_MAX);
 	if (ret == -ENOSYS) {
 		fm->fc->no_getxattr = 1;
 		ret = -EOPNOTSUPP;
@@ -143,7 +143,7 @@  ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
 	}
 	ret = fuse_simple_request(fm, &args);
 	if (!ret && !size)
-		ret = min_t(ssize_t, outarg.size, XATTR_LIST_MAX);
+		ret = min_t(size_t, outarg.size, XATTR_LIST_MAX);
 	if (ret > 0 && size)
 		ret = fuse_verify_xattr_list(list, ret);
 	if (ret == -ENOSYS) {