From: Chris Mason <clm@meta.com>
To: Al Viro <viro@zeniv.linux.org.uk>, bot+bpf-ci@kernel.org
Cc: linux-fsdevel@vger.kernel.org, torvalds@linux-foundation.org,
brauner@kernel.org, jack@suse.cz, raven@themaw.net,
miklos@szeredi.hu, neil@brown.name, a.hindborg@kernel.org,
linux-mm@kvack.org, linux-efi@vger.kernel.org,
ocfs2-devel@lists.linux.dev, kees@kernel.org,
rostedt@goodmis.org, gregkh@linuxfoundation.org,
linux-usb@vger.kernel.org, paul@paul-moore.com,
casey@schaufler-ca.com, linuxppc-dev@lists.ozlabs.org,
john.johansen@canonical.com, selinux@vger.kernel.org,
borntraeger@linux.ibm.com, bpf@vger.kernel.org, ast@kernel.org,
andrii@kernel.org, daniel@iogearbox.net, martin.lau@kernel.org,
eddyz87@gmail.com, yonghong.song@linux.dev,
ihor.solodrai@linux.dev
Subject: Re: [PATCH v3 36/50] functionfs: switch to simple_remove_by_name()
Date: Tue, 11 Nov 2025 22:44:26 -0500 [thread overview]
Message-ID: <e6b90909-fdd7-4c4d-b96e-df27ea9f39c4@meta.com> (raw)
In-Reply-To: <20251111092244.GS2441659@ZenIV>
On 11/11/25 4:22 AM, Al Viro wrote:
> On Tue, Nov 11, 2025 at 07:53:16AM +0000, bot+bpf-ci@kernel.org wrote:
>
>> When ffs_epfiles_create() calls ffs_epfiles_destroy(epfiles, i - 1) after
>> the first ffs_sb_create_file() call fails (when i=1), it passes count=0.
>> The initialization loop starts at i=1, so epfiles[0].ffs is never
>> initialized.
>
> Incorrect. The loop in question is
>
> epfile = epfiles;
> for (i = 1; i <= count; ++i, ++epfile) {
> epfile->ffs = ffs;
> mutex_init(&epfile->mutex);
> mutex_init(&epfile->dmabufs_mutex);
> INIT_LIST_HEAD(&epfile->dmabufs);
> if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
> sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
> else
> sprintf(epfile->name, "ep%u", i);
> err = ffs_sb_create_file(ffs->sb, epfile->name,
> epfile, &ffs_epfile_operations);
> if (err) {
> ffs_epfiles_destroy(epfiles, i - 1);
> return err;
> }
> }
>
> and invariant maintained through the loop is epfile == epfiles + (i - 1).
> We start with i == 1 and epfile == epfiles, modify neither variable in
> the loop body and increment both i and epfile by the same amount in
> the step.
>
> In other words, on the first pass through the loop we access epfiles[0],
> not epfiles[1]. Granted, the loop could've been more idiomatic, but
> it is actually correct.
AI was getting confused about epfile vs epfiles and didn't realize they
were pointing to the same memory. So I put some changes into the prompt
to sort that out, and it found a different variation on this same
complaint.
We're wandering into fuzzing territory here, and I honestly have no idea
if this is a valid use of any of this code, but AI managed to make a
repro that crashes only after your patch. So, I'll let you decide.
The new review:
Can this dereference ZERO_SIZE_PTR when eps_count is 0?
When ffs->eps_count is 0, ffs_epfiles_create() calls kcalloc(0, ...) which
returns ZERO_SIZE_PTR (0x10). The loop never executes so epfiles[0].ffs is
never initialized. Later, cleanup paths (ffs_data_closed and ffs_data_clear)
check if (epfiles) which is true for ZERO_SIZE_PTR, and call
ffs_epfiles_destroy(epfiles, 0).
In the old code, the for loop condition prevented any dereferences when
count=0. In the new code, "root = epfile->ffs->sb->s_root" dereferences
epfile before checking count, which would fault on ZERO_SIZE_PTR.
And the crash:
[ 21.714645] BUG: kernel NULL pointer dereference, address: 0000000000000030
[ 21.714764] #PF: supervisor read access in kernel mode
[ 21.714851] #PF: error_code(0x0000) - not-present page
[ 21.714968] PGD 10abe6067 P4D 10c0fa067 PUD 10864e067 PMD 0
[ 21.715155] Oops: Oops: 0000 [#1] SMP
[ 21.715226] CPU: 15 UID: 0 PID: 1071 Comm: test_ffs_crash Tainted: G E 6.18.0-rc4-g2b3cd169d144 #9 NONE
[ 21.715404] Tainted: [E]=UNSIGNED_MODULE
[ 21.715468] Hardware name: Red Hat KVM, BIOS 1.16.3-4.el9 04/01/2014
[ 21.715583] RIP: 0010:ffs_epfiles_destroy+0xe/0x70
[ 21.715681] Code: 4d ff ff ff 31 ff b8 01 00 00 00 eb 97 66 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 0f 1f 44 00 00 41 55 41 54 49 89 fc 55 53 <48> 8b 47 20 48 8b 80 50 01 00 00 48 8b 68 68 85 f6 74 3a 89 f6 48
[ 21.716055] RSP: 0018:ffa00000024cbe60 EFLAGS: 00010202
[ 21.716167] RAX: 0000000000000246 RBX: ff1100011ae81a00 RCX: ff11000117810540
[ 21.716286] RDX: 0000000000000001 RSI: 0000000000000000 RDI: 0000000000000010
[ 21.716461] RBP: ff1100011ae81a28 R08: ff1100010c1f7ac0 R09: ff11000111499ea0
[ 21.716568] R10: 0000000000000008 R11: 0000000000000000 R12: 0000000000000010
[ 21.716697] R13: ff11000111499ea0 R14: ff1100010ca5d260 R15: 0000000000000000
[ 21.716828] FS: 00007fdb2dbd6740(0000) GS:ff1100089ae74000(0000) knlGS:0000000000000000
[ 21.716992] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 21.717149] CR2: 0000000000000030 CR3: 000000010a2bb004 CR4: 0000000000771ef0
[ 21.717296] PKRU: 55555554
[ 21.717347] Call Trace:
[ 21.717390] <TASK>
[ 21.717434] ffs_data_clear+0xbb/0x140
[ 21.717496] ffs_data_closed+0x8e/0x1d0
[ 21.717565] ffs_ep0_release+0xe/0x20
[ 21.717639] __fput+0xdd/0x2a0
[ 21.717702] __x64_sys_close+0x39/0x70
[ 21.717768] do_syscall_64+0x5d/0x920
[ 21.717873] entry_SYSCALL_64_after_hwframe+0x4b/0x53
And the repro/fuzzer:
/*
* Test program to reproduce FunctionFS crash with eps_count=0
*
* This program creates a USB gadget with no endpoints (only EP0),
* which triggers the ZERO_SIZE_PTR dereference bug in ffs_epfiles_destroy().
*
* Setup:
* mount -t configfs none /sys/kernel/config
* cd /sys/kernel/config/usb_gadget
* mkdir g1 && cd g1
* echo 0x1d6b > idVendor
* echo 0x0104 > idProduct
* mkdir strings/0x409
* echo "1234567890" > strings/0x409/serialnumber
* echo "Manufacturer" > strings/0x409/manufacturer
* echo "Product" > strings/0x409/product
* mkdir configs/c.1
* mkdir configs/c.1/strings/0x409
* echo "Config 1" > configs/c.1/strings/0x409/configuration
* mkdir functions/ffs.test
* mkdir -p /dev/usb-ffs/test
* mount -t functionfs test /dev/usb-ffs/test
*
* Run:
* gcc -o test_ffs_crash test_ffs_crash.c
* ./test_ffs_crash
*
* Expected result on buggy kernel:
* Kernel crash when the program exits (cleanup calls ffs_epfiles_destroy
* with count=0, dereferences ZERO_SIZE_PTR)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <errno.h>
#define FUNCTIONFS_DESCRIPTORS_MAGIC_V2 3
#define FUNCTIONFS_STRINGS_MAGIC 2
/* FunctionFS flags */
#define FUNCTIONFS_HAS_FS_DESC (1 << 0)
#define FUNCTIONFS_HAS_HS_DESC (1 << 1)
/* USB descriptor types */
#define USB_DT_INTERFACE 0x04
struct usb_interface_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints; /* 0 - no endpoints! */
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} __attribute__((packed));
struct ffs_descriptors {
uint32_t magic;
uint32_t length;
uint32_t flags;
uint32_t fs_count; /* Count of FS descriptors */
uint32_t hs_count; /* Count of HS descriptors */
/* Followed by descriptors */
} __attribute__((packed));
struct ffs_strings {
uint32_t magic;
uint32_t length;
uint32_t str_count;
uint32_t lang_count;
} __attribute__((packed));
int main(void)
{
int ep0_fd;
struct {
struct ffs_descriptors header;
struct usb_interface_descriptor fs_intf;
struct usb_interface_descriptor hs_intf;
} __attribute__((packed)) descs;
int ret;
printf("Opening /dev/usb-ffs/test/ep0...\n");
ep0_fd = open("/dev/usb-ffs/test/ep0", O_RDWR);
if (ep0_fd < 0) {
perror("open");
fprintf(stderr, "\nMake sure to setup configfs first:\n");
fprintf(stderr, "See comments at top of source file\n");
return 1;
}
printf("Writing descriptors with interface but NO endpoints (eps_count=0)...\n");
/* Build descriptor structure with interface descriptor but no endpoint descriptors */
memset(&descs, 0, sizeof(descs));
/* Header */
descs.header.magic = FUNCTIONFS_DESCRIPTORS_MAGIC_V2;
descs.header.length = sizeof(descs);
descs.header.flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC;
descs.header.fs_count = 1; /* 1 descriptor for full-speed */
descs.header.hs_count = 1; /* 1 descriptor for high-speed */
/* Full-speed interface descriptor - NO endpoints! */
descs.fs_intf.bLength = sizeof(struct usb_interface_descriptor);
descs.fs_intf.bDescriptorType = USB_DT_INTERFACE;
descs.fs_intf.bInterfaceNumber = 0;
descs.fs_intf.bAlternateSetting = 0;
descs.fs_intf.bNumEndpoints = 0; /* KEY: No endpoints! */
descs.fs_intf.bInterfaceClass = 0xff; /* Vendor specific */
descs.fs_intf.bInterfaceSubClass = 0;
descs.fs_intf.bInterfaceProtocol = 0;
descs.fs_intf.iInterface = 0; /* No string descriptor */
/* High-speed interface descriptor - also NO endpoints! */
descs.hs_intf = descs.fs_intf;
ret = write(ep0_fd, &descs, sizeof(descs));
if (ret < 0) {
perror("write descriptors");
close(ep0_fd);
return 1;
}
printf("Wrote %d bytes of descriptors (interface with bNumEndpoints=0)\n", ret);
printf("Writing strings...\n");
/* Write strings with 1 language, 1 empty string */
struct {
struct ffs_strings header;
uint16_t lang;
char str1[1]; /* Empty string (just null terminator) */
} __attribute__((packed)) strings_data;
memset(&strings_data, 0, sizeof(strings_data));
strings_data.header.magic = FUNCTIONFS_STRINGS_MAGIC;
strings_data.header.length = sizeof(strings_data);
strings_data.header.str_count = 1; /* 1 string */
strings_data.header.lang_count = 1; /* 1 language */
strings_data.lang = 0x0409; /* English */
strings_data.str1[0] = '\0'; /* Empty string */
ret = write(ep0_fd, &strings_data, sizeof(strings_data));
if (ret < 0) {
perror("write strings");
close(ep0_fd);
return 1;
}
printf("Wrote %d bytes of strings\n", ret);
/* Closing the file will trigger cleanup path which calls
* ffs_data_closed() -> ffs_epfiles_destroy(ZERO_SIZE_PTR, 0) and crashes */
close(ep0_fd);
return 0;
}
next prev parent reply other threads:[~2025-11-12 3:45 UTC|newest]
Thread overview: 77+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-11 6:54 [PATCH v3 00/50] tree-in-dcache stuff Al Viro
2025-11-11 6:54 ` [PATCH v3 01/50] fuse_ctl_add_conn(): fix nlink breakage in case of early failure Al Viro
2025-11-11 10:22 ` Miklos Szeredi
2025-11-11 6:54 ` [PATCH v3 02/50] tracefs: fix a leak in eventfs_create_events_dir() Al Viro
2025-11-11 6:54 ` [PATCH v3 03/50] new helper: simple_remove_by_name() Al Viro
2025-11-11 10:29 ` Miklos Szeredi
2025-11-11 6:54 ` [PATCH v3 04/50] new helper: simple_done_creating() Al Viro
2025-11-11 6:54 ` [PATCH v3 05/50] introduce a flag for explicitly marking persistently pinned dentries Al Viro
2025-11-11 6:54 ` [PATCH v3 06/50] primitives for maintaining persisitency Al Viro
2025-11-11 6:54 ` [PATCH v3 07/50] convert simple_{link,unlink,rmdir,rename,fill_super}() to new primitives Al Viro
2025-11-11 6:54 ` [PATCH v3 08/50] convert ramfs and tmpfs Al Viro
2025-11-11 6:54 ` [PATCH v3 09/50] procfs: make /self and /thread_self dentries persistent Al Viro
2025-11-11 6:54 ` [PATCH v3 10/50] configfs, securityfs: kill_litter_super() not needed Al Viro
2025-11-11 6:54 ` [PATCH v3 11/50] convert xenfs Al Viro
2025-11-11 6:54 ` [PATCH v3 12/50] convert smackfs Al Viro
2025-11-11 6:54 ` [PATCH v3 13/50] convert hugetlbfs Al Viro
2025-11-11 6:54 ` [PATCH v3 14/50] convert mqueue Al Viro
2025-11-11 6:54 ` [PATCH v3 15/50] convert bpf Al Viro
2025-11-11 6:54 ` [PATCH v3 16/50] convert dlmfs Al Viro
2025-11-11 6:54 ` [PATCH v3 17/50] convert fuse_ctl Al Viro
2025-11-11 10:28 ` Miklos Szeredi
2025-11-11 6:54 ` [PATCH v3 18/50] convert pstore Al Viro
2025-11-11 6:54 ` [PATCH v3 19/50] convert tracefs Al Viro
2025-11-11 6:54 ` [PATCH v3 20/50] convert debugfs Al Viro
2025-11-11 6:54 ` [PATCH v3 21/50] debugfs: remove duplicate checks in callers of start_creating() Al Viro
2025-11-11 6:54 ` [PATCH v3 22/50] convert efivarfs Al Viro
2025-11-11 6:54 ` [PATCH v3 23/50] convert spufs Al Viro
2025-11-11 6:54 ` [PATCH v3 24/50] convert ibmasmfs Al Viro
2025-11-11 6:54 ` [PATCH v3 25/50] ibmasmfs: get rid of ibmasmfs_dir_ops Al Viro
2025-11-11 6:54 ` [PATCH v3 26/50] convert devpts Al Viro
2025-11-11 6:54 ` [PATCH v3 27/50] binderfs: use simple_start_creating() Al Viro
2025-11-11 6:54 ` [PATCH v3 28/50] binderfs_binder_ctl_create(): kill a bogus check Al Viro
2025-11-11 6:54 ` [PATCH v3 29/50] convert binderfs Al Viro
2025-11-11 6:54 ` [PATCH v3 30/50] autofs_{rmdir,unlink}: dentry->d_fsdata->dentry == dentry there Al Viro
2025-11-11 6:55 ` [PATCH v3 31/50] convert autofs Al Viro
2025-11-11 6:55 ` [PATCH v3 32/50] convert binfmt_misc Al Viro
2025-11-11 6:55 ` [PATCH v3 33/50] selinuxfs: don't stash the dentry of /policy_capabilities Al Viro
2025-11-11 6:55 ` [PATCH v3 34/50] selinuxfs: new helper for attaching files to tree Al Viro
2025-11-11 7:53 ` bot+bpf-ci
2025-11-11 9:49 ` Al Viro
2025-11-12 3:55 ` Chris Mason
2025-11-11 6:55 ` [PATCH v3 35/50] convert selinuxfs Al Viro
2025-11-11 6:55 ` [PATCH v3 36/50] functionfs: switch to simple_remove_by_name() Al Viro
2025-11-11 7:53 ` bot+bpf-ci
2025-11-11 9:22 ` Al Viro
2025-11-11 9:30 ` Christian Brauner
2025-11-11 10:01 ` Al Viro
2025-11-11 14:25 ` Chris Mason
2025-11-12 3:44 ` Chris Mason [this message]
2025-11-13 9:26 ` [functionfs] mainline UAF (was Re: [PATCH v3 36/50] functionfs: switch to simple_remove_by_name()) Al Viro
2025-11-13 21:20 ` Greg Kroah-Hartman
2025-11-14 2:16 ` Chris Mason
2025-11-14 7:58 ` Al Viro
2025-11-14 7:46 ` Al Viro
2025-11-14 11:42 ` Christian Brauner
2025-11-15 13:21 ` Greg Kroah-Hartman
2025-11-16 6:30 ` Al Viro
2025-11-17 22:04 ` Al Viro
2025-11-17 22:04 ` [PATCH 1/4] functionfs: don't abuse ffs_data_closed() on fs shutdown Al Viro
2025-11-17 22:05 ` [PATCH 2/4] functionfs: don't bother with ffs->ref in ffs_data_{opened,closed}() Al Viro
2025-11-17 22:06 ` [PATCH 3/4] functionfs: need to cancel ->reset_work in ->kill_sb() Al Viro
2025-11-17 22:06 ` [PATCH 4/4] functionfs: fix the open/removal races Al Viro
2025-11-18 2:35 ` [functionfs] mainline UAF (was Re: [PATCH v3 36/50] functionfs: switch to simple_remove_by_name()) Greg Kroah-Hartman
2025-11-11 6:55 ` [PATCH v3 37/50] convert functionfs Al Viro
2025-11-11 6:55 ` [PATCH v3 38/50] gadgetfs: switch to simple_remove_by_name() Al Viro
2025-11-11 6:55 ` [PATCH v3 39/50] convert gadgetfs Al Viro
2025-11-11 6:55 ` [PATCH v3 40/50] hypfs: don't pin dentries twice Al Viro
2025-11-11 6:55 ` [PATCH v3 41/50] hypfs: switch hypfs_create_str() to returning int Al Viro
2025-11-11 6:55 ` [PATCH v3 42/50] hypfs: swich hypfs_create_u64() " Al Viro
2025-11-11 6:55 ` [PATCH v3 43/50] convert hypfs Al Viro
2025-11-11 6:55 ` [PATCH v3 44/50] convert rpc_pipefs Al Viro
2025-11-11 6:55 ` [PATCH v3 45/50] convert nfsctl Al Viro
2025-11-11 6:55 ` [PATCH v3 46/50] convert rust_binderfs Al Viro
2025-11-11 6:55 ` [PATCH v3 47/50] get rid of kill_litter_super() Al Viro
2025-11-11 6:55 ` [PATCH v3 48/50] convert securityfs Al Viro
2025-11-11 6:55 ` [PATCH v3 49/50] kill securityfs_recursive_remove() Al Viro
2025-11-11 6:55 ` [PATCH v3 50/50] d_make_discardable(): warn if given a non-persistent dentry Al Viro
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e6b90909-fdd7-4c4d-b96e-df27ea9f39c4@meta.com \
--to=clm@meta.com \
--cc=a.hindborg@kernel.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=borntraeger@linux.ibm.com \
--cc=bot+bpf-ci@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=casey@schaufler-ca.com \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=ihor.solodrai@linux.dev \
--cc=jack@suse.cz \
--cc=john.johansen@canonical.com \
--cc=kees@kernel.org \
--cc=linux-efi@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-usb@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=martin.lau@kernel.org \
--cc=miklos@szeredi.hu \
--cc=neil@brown.name \
--cc=ocfs2-devel@lists.linux.dev \
--cc=paul@paul-moore.com \
--cc=raven@themaw.net \
--cc=rostedt@goodmis.org \
--cc=selinux@vger.kernel.org \
--cc=torvalds@linux-foundation.org \
--cc=viro@zeniv.linux.org.uk \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).