All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 0/2] Fix use-after-free related to binfmt-misc-bpf.c
@ 2026-09-16 18:33 Chris Mason
  2026-09-16 18:33 ` [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release Chris Mason
  2026-09-16 18:33 ` [PATCH RFC 2/2] selftests/bpf: test f_inode walk around bpf_put_file() Chris Mason
  0 siblings, 2 replies; 5+ messages in thread
From: Chris Mason @ 2026-09-16 18:33 UTC (permalink / raw)
  To: mason, bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, brauner

Hi everyone,

Christian asked me to run the recent binfmt_misc_bpf.c changes through
reviews and this bug popped out.  The bug is reproduced, but I'm not at
all confident in the fix.

-chris

Chris Mason (2):
  bpf: fix UAF via trusted BTF walk after release
  selftests/bpf: test f_inode walk around bpf_put_file()

 kernel/bpf/verifier.c                         | 16 +++++++++++
 .../selftests/bpf/progs/verifier_vfs_accept.c | 26 ++++++++++++++++++
 .../selftests/bpf/progs/verifier_vfs_reject.c | 27 +++++++++++++++++++
 3 files changed, 69 insertions(+)

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release
  2026-09-16 18:33 [PATCH RFC 0/2] Fix use-after-free related to binfmt-misc-bpf.c Chris Mason
@ 2026-09-16 18:33 ` Chris Mason
  2026-09-16 18:46   ` Kumar Kartikeya Dwivedi
  2026-09-16 18:33 ` [PATCH RFC 2/2] selftests/bpf: test f_inode walk around bpf_put_file() Chris Mason
  1 sibling, 1 reply; 5+ messages in thread
From: Chris Mason @ 2026-09-16 18:33 UTC (permalink / raw)
  To: mason, bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, brauner

A BPF program can keep dereferencing a trusted pointer that it loaded
from a referenced object after it has released that reference. With a
file from bpf_get_task_exe_file(), f->f_inode stays usable after
bpf_put_file(), and loads through it read freed or reused inode memory.

A pointer derived from a referenced object is only valid while the
reference is held. release_reference() enforces that by invalidating
every register whose id or parent_id matches the released id. However,
check_ptr_to_btf_access() builds the loaded register with
mark_btf_ld_reg(), which is not given the source register and zeroes
both fields. The verifier state for the f_inode case:

    file = bpf_get_task_exe_file(task)   file:  id=N, referenced
    inode = file->f_inode                inode: trusted, id=0,
                                                parent_id=0
    bpf_put_file(file)                   release_reference(N)
                                           skips inode
    inode->i_ino                         accepted, unprobed load

Reproduced by reading inode->i_ino after bpf_put_file() once the task
owning the file had exited and new fds were created.

Fix this by recording the source's reference id in the parent_id of a
trusted PTR_TO_BTF_ID load. A program that dereferences such a pointer
after the release is now rejected with "invalid mem access 'scalar'".

Assisted-by: kres
Fixes: 57539b1c0ac2 ("bpf: Enable annotating trusted nested pointers")
Signed-off-by: Chris Mason <mason@kernel.org>
---
 kernel/bpf/verifier.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6c6b8d8520cd..8b48bf796823 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6219,9 +6219,25 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
 	}
 
 	if (atype == BPF_READ && value_regno >= 0) {
+		u32 link_id = 0;
+
+		/*
+		 * A trusted pointer walked from a referenced object is only
+		 * valid while that reference is held. Remember the reference
+		 * id of the source, or the one it inherited, so that
+		 * release_reference() invalidates the loaded register too.
+		 * Read it now, reg may alias regs[value_regno].
+		 */
+		if (ret == PTR_TO_BTF_ID && (flag & PTR_TRUSTED))
+			link_id = reg_is_referenced(env, reg) ? reg->id : reg->parent_id;
+
 		ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
 		if (ret < 0)
 			return ret;
+
+		/* mark_btf_ld_reg() cleared parent_id, set it afterwards. */
+		if (link_id)
+			regs[value_regno].parent_id = link_id;
 	}
 
 	return 0;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH RFC 2/2] selftests/bpf: test f_inode walk around bpf_put_file()
  2026-09-16 18:33 [PATCH RFC 0/2] Fix use-after-free related to binfmt-misc-bpf.c Chris Mason
  2026-09-16 18:33 ` [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release Chris Mason
@ 2026-09-16 18:33 ` Chris Mason
  1 sibling, 0 replies; 5+ messages in thread
From: Chris Mason @ 2026-09-16 18:33 UTC (permalink / raw)
  To: mason, bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, brauner

No selftest covers a trusted pointer that is loaded from a referenced
object and used after the reference is released. The verifier change
in the previous patch is therefore untested.

The previous patch links a trusted pointer loaded from a referenced
object to that reference through parent_id, so that
release_reference() invalidates it together with its parent. Before
that, a program could keep f->f_inode from a bpf_get_task_exe_file()
file and dereference it after bpf_put_file().

Test both sides of the release on that f_inode walk. The reject test
dereferences the inode after the put and expects "invalid mem access
'scalar'". Without the previous patch the verifier accepts the program,
so the test fails. The accept control reads inode->i_ino while the
reference is held and uses only the scalar after the put, which catches
a verifier that invalidates too much.

Walked pointers have been able to stay trusted since commit
57539b1c0ac2 ("bpf: Enable annotating trusted nested pointers").

Assisted-by: kres
Signed-off-by: Chris Mason <mason@kernel.org>
---
 .../selftests/bpf/progs/verifier_vfs_accept.c | 26 ++++++++++++++++++
 .../selftests/bpf/progs/verifier_vfs_reject.c | 27 +++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
index 55398c04290a..6fb0d2ed3d5f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
@@ -100,4 +100,30 @@ int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
 	return 0;
 }
 
+SEC("lsm.s/file_open")
+__success
+int BPF_PROG(f_inode_walk_used_before_put_file)
+{
+	struct file *acquired;
+	struct inode *inode;
+	ino_t ino;
+
+	acquired = bpf_get_task_exe_file(bpf_get_current_task_btf());
+	if (!acquired)
+		return 0;
+
+	/*
+	 * f_inode walked from a referenced file can be dereferenced while
+	 * the file reference is held. Only the scalar read from it is used
+	 * after the put.
+	 */
+	inode = acquired->f_inode;
+	ino = inode->i_ino;
+	bpf_put_file(acquired);
+
+	if (ino == 0)
+		return -EACCES;
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index b5f456d57669..536acbe15357 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -173,4 +173,31 @@ int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
 		return -EACCES;
 	return 0;
 }
+
+SEC("lsm.s/file_open")
+__failure __msg("invalid mem access 'scalar'")
+int BPF_PROG(f_inode_walk_used_after_put_file)
+{
+	struct file *acquired;
+	struct inode *inode;
+	ino_t ino;
+
+	acquired = bpf_get_task_exe_file(bpf_get_current_task_btf());
+	if (!acquired)
+		return 0;
+
+	/*
+	 * f_inode walked from a referenced file is trusted, but only for
+	 * as long as the file reference is held.
+	 */
+	inode = acquired->f_inode;
+	bpf_put_file(acquired);
+
+	/* The put invalidated inode, it can't be dereferenced anymore. */
+	ino = inode->i_ino;
+	if (ino == 0)
+		return -EACCES;
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release
  2026-09-16 18:33 ` [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release Chris Mason
@ 2026-09-16 18:46   ` Kumar Kartikeya Dwivedi
  2026-09-16 19:19     ` Chris Mason
  0 siblings, 1 reply; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 18:46 UTC (permalink / raw)
  To: Chris Mason, bpf; +Cc: ast, daniel, andrii, eddyz87, brauner

On Wed Sep 16, 2026 at 8:33 PM CEST, Chris Mason wrote:
> A BPF program can keep dereferencing a trusted pointer that it loaded
> from a referenced object after it has released that reference. With a
> file from bpf_get_task_exe_file(), f->f_inode stays usable after
> bpf_put_file(), and loads through it read freed or reused inode memory.
>
> A pointer derived from a referenced object is only valid while the
> reference is held. release_reference() enforces that by invalidating
> every register whose id or parent_id matches the released id. However,
> check_ptr_to_btf_access() builds the loaded register with
> mark_btf_ld_reg(), which is not given the source register and zeroes
> both fields. The verifier state for the f_inode case:
>
>     file = bpf_get_task_exe_file(task)   file:  id=N, referenced
>     inode = file->f_inode                inode: trusted, id=0,
>                                                 parent_id=0
>     bpf_put_file(file)                   release_reference(N)
>                                            skips inode
>     inode->i_ino                         accepted, unprobed load
>
> Reproduced by reading inode->i_ino after bpf_put_file() once the task
> owning the file had exited and new fds were created.
>
> Fix this by recording the source's reference id in the parent_id of a
> trusted PTR_TO_BTF_ID load. A program that dereferences such a pointer
> after the release is now rejected with "invalid mem access 'scalar'".
>
> Assisted-by: kres
> Fixes: 57539b1c0ac2 ("bpf: Enable annotating trusted nested pointers")
> Signed-off-by: Chris Mason <mason@kernel.org>
> ---

Thanks for the report. I have a fix for this locally in the queue. The diagnosis
is correct, but there are more places that would require changes. For instance,
bpf_real_data_inode() should, but does not, propagate the lifetime of the input
argument to the returned inode. That would require some sort of annotation so
that we are aware of the lifetime relationship between the input and the
returned pointer.

I will send it out and will handle this case as well.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release
  2026-09-16 18:46   ` Kumar Kartikeya Dwivedi
@ 2026-09-16 19:19     ` Chris Mason
  0 siblings, 0 replies; 5+ messages in thread
From: Chris Mason @ 2026-09-16 19:19 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf; +Cc: ast, daniel, andrii, eddyz87, brauner

On Wed, Sep 16, 2026, at 2:46 PM, Kumar Kartikeya Dwivedi wrote:
> On Wed Sep 16, 2026 at 8:33 PM CEST, Chris Mason wrote:
>> A BPF program can keep dereferencing a trusted pointer that it loaded
>> from a referenced object after it has released that reference. With a
>> file from bpf_get_task_exe_file(), f->f_inode stays usable after
>> bpf_put_file(), and loads through it read freed or reused inode memory.
>>
>> A pointer derived from a referenced object is only valid while the
>> reference is held. release_reference() enforces that by invalidating
>> every register whose id or parent_id matches the released id. However,
>> check_ptr_to_btf_access() builds the loaded register with
>> mark_btf_ld_reg(), which is not given the source register and zeroes
>> both fields. The verifier state for the f_inode case:
>>
>>     file = bpf_get_task_exe_file(task)   file:  id=N, referenced
>>     inode = file->f_inode                inode: trusted, id=0,
>>                                                 parent_id=0
>>     bpf_put_file(file)                   release_reference(N)
>>                                            skips inode
>>     inode->i_ino                         accepted, unprobed load
>>
>> Reproduced by reading inode->i_ino after bpf_put_file() once the task
>> owning the file had exited and new fds were created.
>>
>> Fix this by recording the source's reference id in the parent_id of a
>> trusted PTR_TO_BTF_ID load. A program that dereferences such a pointer
>> after the release is now rejected with "invalid mem access 'scalar'".
>>
>> Assisted-by: kres
>> Fixes: 57539b1c0ac2 ("bpf: Enable annotating trusted nested pointers")
>> Signed-off-by: Chris Mason <mason@kernel.org>
>> ---
>
> Thanks for the report. I have a fix for this locally in the queue. The diagnosis
> is correct, but there are more places that would require changes. For instance,
> bpf_real_data_inode() should, but does not, propagate the lifetime of the input
> argument to the returned inode. That would require some sort of annotation so
> that we are aware of the lifetime relationship between the input and the
> returned pointer.
>
> I will send it out and will handle this case as well.

Perfect, thank you.

-chris

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-16 19:20 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16 18:33 [PATCH RFC 0/2] Fix use-after-free related to binfmt-misc-bpf.c Chris Mason
2026-09-16 18:33 ` [PATCH RFC 1/2] bpf: fix UAF via trusted BTF walk after release Chris Mason
2026-09-16 18:46   ` Kumar Kartikeya Dwivedi
2026-09-16 19:19     ` Chris Mason
2026-09-16 18:33 ` [PATCH RFC 2/2] selftests/bpf: test f_inode walk around bpf_put_file() Chris Mason

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.