linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
From: Al Viro <viro@zeniv.linux.org.uk>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: bot+bpf-ci@kernel.org, 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, 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,
	Chris Mason <clm@meta.com>
Subject: Re: [functionfs] mainline UAF (was Re: [PATCH v3 36/50] functionfs: switch to simple_remove_by_name())
Date: Fri, 14 Nov 2025 07:46:14 +0000	[thread overview]
Message-ID: <20251114074614.GY2441659@ZenIV> (raw)
In-Reply-To: <2025111316-cornfield-sphinx-ba89@gregkh>

On Thu, Nov 13, 2025 at 04:20:08PM -0500, Greg Kroah-Hartman wrote:

> Sorry for the delay.  Yes, we should be grabing the mutex in there, good
> catch.  There's been more issues pointed out with the gadget code in the
> past year or so as more people are starting to actually use it and
> stress it more.  So if you have a patch for this, I'll gladly take it :)

How about the following?

commit 330837c8101578438f64cfaec3fb85521d668e56
Author: Al Viro <viro@zeniv.linux.org.uk>
Date:   Fri Nov 14 02:18:22 2025 -0500

    functionfs: fix the open/removal races
    
    ffs_epfile_open() can race with removal, ending up with file->private_data
    pointing to freed object.
    
    There is a total count of opened files on functionfs (both ep0 and
    dynamic ones) and when it hits zero, dynamic files get removed.
    Unfortunately, that removal can happen while another thread is
    in ffs_epfile_open(), but has not incremented the count yet.
    In that case open will succeed, leaving us with UAF on any subsequent
    read() or write().
    
    The root cause is that ffs->opened is misused; atomic_dec_and_test() vs.
    atomic_add_return() is not a good idea, when object remains visible all
    along.
    
    To untangle that
            * serialize openers on ffs->mutex (both for ep0 and for dynamic files)
            * have dynamic ones use atomic_inc_not_zero() and fail if we had
    zero ->opened; in that case the file we are opening is doomed.
            * have the inodes of dynamic files marked on removal (from the
    callback of simple_recursive_removal()) - clear ->i_private there.
            * have open of dynamic ones verify they hadn't been already removed,
    along with checking that state is FFS_ACTIVE.
    
    Fix another abuse of ->opened, while we are at it - it starts equal to 0,
    is incremented on opens and decremented on ->release()... *and* decremented
    (always from 0 to -1) in ->kill_sb().  Handling that case has no business
    in ffs_data_closed() (or to ->opened); just have ffs_kill_sb() do what
    ffs_data_closed() would in case of decrement to negative rather than
    calling ffs_data_closed() there.
    
    And don't bother with bumping ffs->ref when opening a file - superblock
    already holds the reference and it won't go away while there are any opened
    files on the filesystem.
    
    Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 47cfbe41fdff..ed7fa869ea77 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -640,13 +640,22 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
 
 static int ffs_ep0_open(struct inode *inode, struct file *file)
 {
-	struct ffs_data *ffs = inode->i_private;
+	struct ffs_data *ffs = inode->i_sb->s_fs_info;
+	int ret;
 
-	if (ffs->state == FFS_CLOSING)
-		return -EBUSY;
+	/* Acquire mutex */
+	ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
+	if (ret < 0)
+		return ret;
 
-	file->private_data = ffs;
 	ffs_data_opened(ffs);
+	if (ffs->state == FFS_CLOSING) {
+		ffs_data_closed(ffs);
+		mutex_unlock(&ffs->mutex);
+		return -EBUSY;
+	}
+	mutex_unlock(&ffs->mutex);
+	file->private_data = ffs;
 
 	return stream_open(inode, file);
 }
@@ -1193,14 +1202,33 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
 static int
 ffs_epfile_open(struct inode *inode, struct file *file)
 {
-	struct ffs_epfile *epfile = inode->i_private;
+	struct ffs_data *ffs = inode->i_sb->s_fs_info;
+	struct ffs_epfile *epfile;
+	int ret;
 
-	if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
+	/* Acquire mutex */
+	ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
+	if (ret < 0)
+		return ret;
+
+	if (!atomic_inc_not_zero(&ffs->opened)) {
+		mutex_unlock(&ffs->mutex);
 		return -ENODEV;
+	}
+	/*
+	 * we want the state to be FFS_ACTIVE; FFS_ACTIVE alone is
+	 * not enough, though - we might have been through FFS_CLOSING
+	 * and back to FFS_ACTIVE, with our file already removed.
+	 */
+	epfile = smp_load_acquire(&inode->i_private);
+	if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) {
+		mutex_unlock(&ffs->mutex);
+		ffs_data_closed(ffs);
+		return -ENODEV;
+	}
+	mutex_unlock(&ffs->mutex);
 
 	file->private_data = epfile;
-	ffs_data_opened(epfile->ffs);
-
 	return stream_open(inode, file);
 }
 
@@ -1332,7 +1360,7 @@ static void ffs_dmabuf_put(struct dma_buf_attachment *attach)
 static int
 ffs_epfile_release(struct inode *inode, struct file *file)
 {
-	struct ffs_epfile *epfile = inode->i_private;
+	struct ffs_epfile *epfile = file->private_data;
 	struct ffs_dmabuf_priv *priv, *tmp;
 	struct ffs_data *ffs = epfile->ffs;
 
@@ -2071,12 +2099,18 @@ static int ffs_fs_init_fs_context(struct fs_context *fc)
 	return 0;
 }
 
+static void ffs_data_reset(struct ffs_data *ffs);
+
 static void
 ffs_fs_kill_sb(struct super_block *sb)
 {
 	kill_litter_super(sb);
-	if (sb->s_fs_info)
-		ffs_data_closed(sb->s_fs_info);
+	if (sb->s_fs_info) {
+		struct ffs_data *ffs = sb->s_fs_info;
+		ffs->state = FFS_CLOSING;
+		ffs_data_reset(ffs);
+		ffs_data_put(ffs);
+	}
 }
 
 static struct file_system_type ffs_fs_type = {
@@ -2114,7 +2148,6 @@ static void functionfs_cleanup(void)
 /* ffs_data and ffs_function construction and destruction code **************/
 
 static void ffs_data_clear(struct ffs_data *ffs);
-static void ffs_data_reset(struct ffs_data *ffs);
 
 static void ffs_data_get(struct ffs_data *ffs)
 {
@@ -2123,7 +2156,6 @@ static void ffs_data_get(struct ffs_data *ffs)
 
 static void ffs_data_opened(struct ffs_data *ffs)
 {
-	refcount_inc(&ffs->ref);
 	if (atomic_add_return(1, &ffs->opened) == 1 &&
 			ffs->state == FFS_DEACTIVATED) {
 		ffs->state = FFS_CLOSING;
@@ -2148,11 +2180,11 @@ static void ffs_data_put(struct ffs_data *ffs)
 
 static void ffs_data_closed(struct ffs_data *ffs)
 {
-	struct ffs_epfile *epfiles;
-	unsigned long flags;
-
 	if (atomic_dec_and_test(&ffs->opened)) {
 		if (ffs->no_disconnect) {
+			struct ffs_epfile *epfiles;
+			unsigned long flags;
+
 			ffs->state = FFS_DEACTIVATED;
 			spin_lock_irqsave(&ffs->eps_lock, flags);
 			epfiles = ffs->epfiles;
@@ -2171,12 +2203,6 @@ static void ffs_data_closed(struct ffs_data *ffs)
 			ffs_data_reset(ffs);
 		}
 	}
-	if (atomic_read(&ffs->opened) < 0) {
-		ffs->state = FFS_CLOSING;
-		ffs_data_reset(ffs);
-	}
-
-	ffs_data_put(ffs);
 }
 
 static struct ffs_data *ffs_data_new(const char *dev_name)
@@ -2352,6 +2378,11 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
 	return 0;
 }
 
+static void clear_one(struct dentry *dentry)
+{
+	smp_store_release(&dentry->d_inode->i_private, NULL);
+}
+
 static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
 {
 	struct ffs_epfile *epfile = epfiles;
@@ -2359,7 +2390,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
 	for (; count; --count, ++epfile) {
 		BUG_ON(mutex_is_locked(&epfile->mutex));
 		if (epfile->dentry) {
-			simple_recursive_removal(epfile->dentry, NULL);
+			simple_recursive_removal(epfile->dentry, clear_one);
 			epfile->dentry = NULL;
 		}
 	}


  parent reply	other threads:[~2025-11-14  7:46 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
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 [this message]
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=20251114074614.GY2441659@ZenIV \
    --to=viro@zeniv.linux.org.uk \
    --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=clm@meta.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=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).