[BUG] general protection fault in path_put

Jaeyoung Chung posted 1 patch 1 month ago
[BUG] general protection fault in path_put
Posted by Jaeyoung Chung 1 month ago
Hello,

We found a "general protection fault in path_put" on Linux v7.2.
The issue was found by our own race fuzzer. We have not analyzed the root cause,
so we do not have a proposed fix to offer.

To reproduce the race reliably, we applied the delay patch below to the
kernel and ran the C reproducer as root inside an x86_64 QEMU guest. The
crash log we observed, the delay patch and the reproducer are all included
below.

The following kernel config options are required to reproduce the issue:
    CONFIG_UPROBES=y
    CONFIG_UPROBE_EVENTS=y
    CONFIG_TRACING=y
    CONFIG_DEBUG_FS=y
    CONFIG_FAULT_INJECTION=y
    CONFIG_FAILSLAB=y
    CONFIG_FAULT_INJECTION_DEBUG_FS=y
    CONFIG_KASAN=y

We hope this report is useful. Please let us know if any further
information would help.

Reported-by: Eulgyu Kim <eulgyukim@snu.ac.kr>
Reported-by: Jaeyoung Chung <jjy600901@snu.ac.kr>

Kernel delay patch:
==================================================================
diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c
index c274346853d1..203ff3d601dc 100644
--- a/kernel/trace/trace_uprobe.c
+++ b/kernel/trace/trace_uprobe.c
@@ -10,6 +10,7 @@
 #include <linux/bpf-cgroup.h>
 #include <linux/cleanup.h>
 #include <linux/ctype.h>
+#include <linux/delay.h>
 #include <linux/filter.h>
 #include <linux/module.h>
 #include <linux/namei.h>
@@ -339,6 +340,9 @@ alloc_trace_uprobe(const char *group, const char *event, int nargs, bool is_ret)
 	int ret;
 
 	tu = kzalloc_flex(*tu, tp.args, nargs);
+	if (!tu && !strncmp(current->comm, "syzrepro", 8)) {
+		mdelay(10);
+	}
 	if (!tu)
 		return ERR_PTR(-ENOMEM);
 
@@ -360,6 +364,9 @@ alloc_trace_uprobe(const char *group, const char *event, int nargs, bool is_ret)
 	return tu;
 
 error:
+	if (!strncmp(current->comm, "syzrepro", 8)) {
+		mdelay(10);
+	}
 	free_percpu(tu->nhits);
 	kfree(tu);
 
@@ -371,6 +378,9 @@ static void free_trace_uprobe(struct trace_uprobe *tu)
 	if (!tu)
 		return;
 
+	if ((unsigned long)tu >= 0xfffffffffffff000UL) {
+		mdelay(10);
+	}
 	path_put(&tu->path);
 	trace_probe_cleanup(&tu->tp);
 	kfree(tu->filename);
@@ -686,6 +696,10 @@ static int __trace_uprobe_create(int argc, const char **argv)
 	argv += 2;
 
 	tu = alloc_trace_uprobe(group, event, argc, is_return);
+	if (!strncmp(current->comm, "syzrepro", 8) &&
+	    (unsigned long)tu >= 0xfffffffffffff000UL) {
+		mdelay(10);
+	}
 	if (IS_ERR(tu)) {
 		ret = PTR_ERR(tu);
 		/* This must return -ENOMEM otherwise there is a bug */

==================================================================

C reproducer:
==================================================================
#define _GNU_SOURCE
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <unistd.h>

#define SYSCHK(x) ({ long __r = (long)(x); if (__r == -1L) { perror(#x); exit(1); } __r; })

/*
 * Register a uprobe while failslab fails the n-th kernel allocation.  The error
 * path hands the ERR_PTR straight to the cleanup, and path_put() walks over it.
 * g_hot holds the n values that actually crashed, tried first.
 */
static const int g_hot[] = { 8, 9, 10, 11, 7, 12, 6, 13, 5, 14 };
#define NHOT ((int)(sizeof(g_hot) / sizeof(g_hot[0])))
#define NSWEEP 40

static char g_events[256];
static char g_target[256];

static void write_file(const char *path, const char *val)
{
	int fd = SYSCHK(open(path, O_WRONLY | O_CLOEXEC));

	SYSCHK(write(fd, val, strlen(val)));
	close(fd);
}

static void setup_tracefs(void)
{
	static const char *const c[] = { "/sys/kernel/tracing/uprobe_events",
					 "/sys/kernel/debug/tracing/uprobe_events" };
	unsigned i;
	int fd;

	for (i = 0; i < 2; i++) {
		fd = open(c[i], O_WRONLY | O_CLOEXEC);
		if (fd >= 0) {
			close(fd);
			snprintf(g_events, sizeof(g_events), "%s", c[i]);
			return;
		}
	}
	mkdir("/syztracing", 0755);
	mount("none", "/syztracing", "tracefs", 0, NULL);
	snprintf(g_events, sizeof(g_events), "/syztracing/uprobe_events");
	close(SYSCHK(open(g_events, O_WRONLY | O_CLOEXEC)));
}

static void setup_target(void)
{
	char buf[4096];
	int fd;

	memset(buf, 0x90, sizeof(buf));
	snprintf(g_target, sizeof(g_target), "/root/syzrepro_f0");
	fd = SYSCHK(open(g_target, O_CREAT | O_RDWR | O_CLOEXEC, 0755));
	SYSCHK(write(fd, buf, sizeof(buf)));
	close(fd);
}

static void set_fail_nth(int fd, int n)
{
	char b[16];

	write(fd, b, snprintf(b, sizeof(b), "%d", n));
}

static void *worker(void *p)
{
	char name[16], cmd[192];
	int idx = (int)(long)p, fnfd, evfd, it, k, n, len;

	snprintf(name, sizeof(name), "syzrepro%d", idx);
	prctl(PR_SET_NAME, name, 0, 0, 0);
	fnfd = SYSCHK(open("/proc/thread-self/fail-nth", O_RDWR | O_CLOEXEC));
	evfd = SYSCHK(open(g_events, O_WRONLY | O_CLOEXEC));

	for (it = 0; it < 64; it++) {
		for (k = 0; k < NHOT + NSWEEP; k++) {
			n = k < NHOT ? g_hot[k] : k - NHOT + 1;
			len = snprintf(cmd, sizeof(cmd), "p:s%1d%03d%03d %s:0",
				       idx, it % 1000, n % 1000, g_target);
			set_fail_nth(fnfd, n);
			write(evfd, cmd, len);
			set_fail_nth(fnfd, 0);
		}
	}
	close(evfd);
	close(fnfd);
	return NULL;
}

int main(void)
{
	pthread_t th[2];
	long i;

	mkdir("/sys/kernel/debug", 0755);
	mount("debugfs", "/sys/kernel/debug", "debugfs", 0, NULL);
	write_file("/sys/kernel/debug/failslab/ignore-gfp-wait", "N");
	setup_tracefs();
	setup_target();

	for (i = 0; i < 2; i++)
		pthread_create(&th[i], NULL, worker, (void *)i);
	for (i = 0; i < 2; i++)
		pthread_join(th[i], NULL);
	return 0;
}
==================================================================

Crash log:
==================================================================
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000008: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047]
CPU: 0 UID: 0 PID: 399 Comm: syzrepro0 Not tainted 7.2.0-dirty #3 PREEMPT 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
RIP: 0010:path_put+0x24/0x60 fs/namei.c:722
Code: 90 90 90 90 90 90 f3 0f 1e fa 0f 1f 44 00 00 41 56 53 48 89 fb 49 be 00 00 00 00 00 fc ff df 48 83 c7 08 48 89 f8 48 c1 e8 03 <42> 80 3c 30 00 74 05 e8 c0 b0 f3 ff 48 8b 7b 08 e8 a7 ac 02 00 48
RSP: 0018:ffff88810c157c60 EFLAGS: 00010203
RAX: 0000000000000008 RBX: 000000000000003c RCX: 0000000000248906
RDX: 0000000000248964 RSI: 0000000b0f0a1740 RDI: 0000000000000044
RBP: ffff8881015b3d08 R08: 0000000000000000 R09: 0000000000000000
R10: ffffffffa557aa00 R11: ffffffffa19cb170 R12: 0000000000000002
R13: 00000000fffffff4 R14: dffffc0000000000 R15: ffffffffa218f5c0
FS:  00007a12c63726c0(0000) GS:ffff88817575f000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007a12c63ed200 CR3: 0000000107ea8000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 free_trace_uprobe+0x8f/0xf0 kernel/trace/trace_uprobe.c:384
 __free_free_trace_uprobe kernel/trace/trace_uprobe.c:546 [inline]
 __trace_uprobe_create+0x222/0xb50 kernel/trace/trace_uprobe.c:739
 trace_probe_create+0x52/0x90 kernel/trace/trace_probe.c:2371
 dyn_event_create+0x48/0x70 kernel/trace/trace_dynevent.c:128
 create_or_delete_trace_uprobe+0x3c/0x70 kernel/trace/trace_uprobe.c:753
 trace_parse_run_command+0x19d/0x2c0 kernel/trace/trace.c:9565
 vfs_write+0x20d/0xa10 fs/read_write.c:685
 ksys_write+0xb0/0x170 fs/read_write.c:739
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf7/0x370 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7a12c646e38f
Code: 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 a9 d4 f8 ff 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 48 89 44 24 08 e8 fc d4 f8 ff 48
RSP: 002b:00007a12c6371d80 EFLAGS: 00000293 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007a12c6371dd0 RCX: 00007a12c646e38f
RDX: 000000000000001e RSI: 00007a12c6371dd0 RDI: 0000000000000005
RBP: 000000000000001e R08: 0000000000000000 R09: 0000000000000064
R10: 00007a12c6371ab7 R11: 0000000000000293 R12: 0000000000000000
R13: 0000000000000003 R14: 0000000000000008 R15: 0000000000000000
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:path_put+0x24/0x60 fs/namei.c:722
Code: 90 90 90 90 90 90 f3 0f 1e fa 0f 1f 44 00 00 41 56 53 48 89 fb 49 be 00 00 00 00 00 fc ff df 48 83 c7 08 48 89 f8 48 c1 e8 03 <42> 80 3c 30 00 74 05 e8 c0 b0 f3 ff 48 8b 7b 08 e8 a7 ac 02 00 48
RSP: 0018:ffff88810c157c60 EFLAGS: 00010203
RAX: 0000000000000008 RBX: 000000000000003c RCX: 0000000000248906
RDX: 0000000000248964 RSI: 0000000b0f0a1740 RDI: 0000000000000044
RBP: ffff8881015b3d08 R08: 0000000000000000 R09: 0000000000000000
R10: ffffffffa557aa00 R11: ffffffffa19cb170 R12: 0000000000000002
R13: 00000000fffffff4 R14: dffffc0000000000 R15: ffffffffa218f5c0
FS:  00007a12c63726c0(0000) GS:ffff88817575f000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007a12c63ed200 CR3: 0000000107ea8000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
   0:	90                   	nop
   1:	90                   	nop
   2:	90                   	nop
   3:	90                   	nop
   4:	90                   	nop
   5:	90                   	nop
   6:	f3 0f 1e fa          	endbr64
   a:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
   f:	41 56                	push   %r14
  11:	53                   	push   %rbx
  12:	48 89 fb             	mov    %rdi,%rbx
  15:	49 be 00 00 00 00 00 	movabs $0xdffffc0000000000,%r14
  1c:	fc ff df
  1f:	48 83 c7 08          	add    $0x8,%rdi
  23:	48 89 f8             	mov    %rdi,%rax
  26:	48 c1 e8 03          	shr    $0x3,%rax
* 2a:	42 80 3c 30 00       	cmpb   $0x0,(%rax,%r14,1) <-- trapping instruction
  2f:	74 05                	je     0x36
  31:	e8 c0 b0 f3 ff       	callq  0xfff3b0f6
  36:	48 8b 7b 08          	mov    0x8(%rbx),%rdi
  3a:	e8 a7 ac 02 00       	callq  0x2ace6
  3f:	48                   	rex.W
==================================================================
[PATCH] USB: gadget: fix NULL pointer dereference in gadget_dev_ioctl()
Posted by Lovekesh Solanki 1 month ago
gadget_dev_ioctl() reads dev->gadget outside the dev->lock, while
gadgetfs_bind() writes it without holding the lock. A concurrent
bind can update dev->gadget and dev->state under the lock while the
ioctl thread holds a stale NULL copy, causing a NULL pointer
dereference at offset 0x28 (gadget->ops->ioctl).

Read dev->gadget inside the locked region, before the state check,
so the state and gadget pointer are always consistent.

Cc: stable@vger.kernel.org
Reported-by: Eulgyu Kim <eulgyukim@snu.ac.kr>
Link: https://lore.kernel.org/all/20260824160022.2378192-1-jjy600901@snu.ac.kr/
Reported-by: Jaeyoung Chung <jjy600901@snu.ac.kr>
Link: https://lore.kernel.org/all/20260824160022.2378192-1-jjy600901@snu.ac.kr/
Signed-off-by: Lovekesh Solanki <lovekeshsolanki00@gmail.com>
---
 drivers/usb/gadget/legacy/inode.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c
index d87a8ab51510..e9f7d7c1a6a3 100644
--- a/drivers/usb/gadget/legacy/inode.c
+++ b/drivers/usb/gadget/legacy/inode.c
@@ -1251,14 +1251,15 @@ ep0_poll (struct file *fd, poll_table *wait)
 static long gadget_dev_ioctl (struct file *fd, unsigned code, unsigned long value)
 {
 	struct dev_data		*dev = fd->private_data;
-	struct usb_gadget	*gadget = dev->gadget;
+	struct usb_gadget	*gadget;
 	long ret = -ENOTTY;
 
 	spin_lock_irq(&dev->lock);
+	gadget = dev->gadget;
 	if (dev->state == STATE_DEV_OPENED ||
 			dev->state == STATE_DEV_UNBOUND) {
 		/* Not bound to a UDC */
-	} else if (gadget->ops->ioctl) {
+	} else if (gadget && gadget->ops->ioctl) {
 		++dev->udc_usage;
 		spin_unlock_irq(&dev->lock);
 
-- 
2.55.0
Re: [PATCH] USB: gadget: fix NULL pointer dereference in gadget_dev_ioctl()
Posted by Lovekesh Solanki 1 month ago
This patch was intended for a different report. Please ignore it.

Sorry for the confusion.

Thanks,
Lovekesh