linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
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;
}



  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).