[BUG] WARNING: refcount bug in dvb_device_open

Jaeyoung Chung posted 1 patch 1 month ago
[BUG] WARNING: refcount bug in dvb_device_open
Posted by Jaeyoung Chung 1 month ago
Hello,

We found a "WARNING: refcount bug in dvb_device_open" 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_MEDIA_SUPPORT=y
    CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y
    CONFIG_DVB_CORE=y
    CONFIG_DVB_VIDTV=y
    # CONFIG_DVB_DYNAMIC_MINORS is not set
    CONFIG_DVB_TEST_DRIVERS=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:
==================================================================
--- a/drivers/media/dvb-core/dvbdev.c	2026-08-21 00:29:05.580120458 +0900
+++ b/drivers/media/dvb-core/dvbdev.c	2026-08-21 00:29:05.652121379 +0900
@@ -21,6 +21,8 @@
 #include <linux/fs.h>
 #include <linux/cdev.h>
 #include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
 #include <media/dvbdev.h>
 
 /* Due to enum tuner_pad_index */
@@ -105,6 +111,9 @@
 			goto fail;
 		file->private_data = dvb_device_get(dvbdev);
 		replace_fops(file, new_fops);
+		if (strncmp(current->comm, "syzrepro1", 9) == 0) {
+			mdelay(20);
+		}
 		if (file->f_op->open)
 			err = file->f_op->open(inode, file);
 		up_read(&minor_rwsem);
@@ -613,6 +625,9 @@
 {
 	struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref);
 
+	if (strncmp(current->comm, "syzrepro", 8) == 0) {
+		mdelay(60);
+	}
 	kfree(dvbdev);
 }
 
==================================================================

C reproducer:
==================================================================
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <unistd.h>

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

#define DVB_MAJOR 212
#define FE_MINOR(a, id) (((a) << 6) | ((id) << 4) | 3)

struct k_sigevent {
	uint64_t sigev_value;
	int sigev_signo, sigev_notify, sigev_tid, _pad[12];
};

static char g_path[256];
static pid_t g_pid;
static volatile pid_t g_t1;
static volatile int g_stop;

static int usable(const char *p)
{
	int fd = open(p, O_RDONLY | O_NONBLOCK);

	if (fd >= 0) {
		close(fd);
		return 1;
	}
	return errno == EBUSY;
}

static int find_frontend(void)
{
	int a, id;

	for (a = 0; a < 4; a++) {
		for (id = 0; id < 4; id++) {
			snprintf(g_path, sizeof(g_path),
				 "/dev/dvb/adapter%d/frontend%d", a, id);
			if (usable(g_path))
				return 0;
			snprintf(g_path, sizeof(g_path), "/tmp/fe%d", FE_MINOR(a, id));
			unlink(g_path);
			if (mknod(g_path, S_IFCHR | 0600,
				  makedev(DVB_MAJOR, FE_MINOR(a, id))) == 0 && usable(g_path))
				return 0;
			unlink(g_path);
		}
	}
	return -1;
}

static void noop(int sig) { (void)sig; }

static void *opener(void *a)
{
	(void)a;
	prctl(PR_SET_NAME, "syzrepro0", 0, 0, 0);
	while (!g_stop) {
		int fd = open(g_path, O_RDONLY | O_NONBLOCK);

		if (fd >= 0)
			close(fd);
	}
	return NULL;
}

/* T1: takes a signal inside dvb_device_open(), unwinding the open midway */
static void *victim(void *a)
{
	struct k_sigevent sev = { .sigev_signo = SIGUSR1, .sigev_notify = 4 };
	struct itimerspec its = { { 0, 3000000 }, { 0, 3000000 } };
	int timerid = 0;
	long i;

	(void)a;
	prctl(PR_SET_NAME, "syzrepro1", 0, 0, 0);
	g_t1 = sev.sigev_tid = syscall(__NR_gettid);
	if (syscall(__NR_timer_create, CLOCK_MONOTONIC, &sev, &timerid) == 0)
		syscall(__NR_timer_settime, timerid, 0, &its, NULL);

	for (i = 0; i < 40 && !g_stop; i++) {
		int fd = open(g_path, O_RDWR | O_NOATIME);

		if (fd >= 0)
			close(fd);
	}
	g_stop = 1;
	return NULL;
}

static void *signaller(void *a)
{
	(void)a;
	prctl(PR_SET_NAME, "syzrepro2", 0, 0, 0);
	while (!g_stop) {
		if (g_t1)
			syscall(__NR_tgkill, g_pid, g_t1, SIGUSR1);
		usleep(300);
	}
	return NULL;
}

int main(void)
{
	struct sigaction sa = { .sa_handler = noop };
	pthread_t a, b, c;

	prctl(PR_SET_NAME, "syzrepro3", 0, 0, 0);
	g_pid = getpid();
	if (find_frontend() != 0) {
		fprintf(stderr, "no dvb frontend\n");
		return 1;
	}
	SYSCHK(sigaction(SIGUSR1, &sa, NULL));
	pthread_create(&b, NULL, victim, NULL);
	pthread_create(&c, NULL, signaller, NULL);
	pthread_create(&a, NULL, opener, NULL);
	pthread_join(b, NULL);
	g_stop = 1;
	pthread_join(c, NULL);
	pthread_join(a, NULL);
	return 0;
}
==================================================================

Crash log:
==================================================================
refcount_t: addition on 0; use-after-free.
WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x76/0xd0 lib/refcount.c:25, CPU#2: syzrepro0/411
Modules linked in:
CPU: 2 UID: 0 PID: 411 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:refcount_warn_saturate+0x76/0xd0 lib/refcount.c:25
Code: 3d ff af bd 04 67 48 0f b9 3a eb 4d 85 db 74 2f 83 fb 01 75 38 48 8d 3d f8 af bd 04 67 48 0f b9 3a eb 36 48 8d 3d fa af bd 04 <67> 48 0f b9 3a eb 28 48 8d 3d fc af bd 04 67 48 0f b9 3a eb 1a 48
RSP: 0018:ffff88810ab6f9e8 EFLAGS: 00010246
RAX: 0000000000000000 RBX: 0000000000000002 RCX: dffffc0000000000
RDX: 0000000000000001 RSI: 0000000000000004 RDI: ffffffffbce53340
RBP: 00000000ffffffed R08: ffff88810344ff13 R09: 1ffff11020689fe2
R10: dffffc0000000000 R11: ffffed1020689fe3 R12: ffff88810344ff10
R13: ffff88810409a000 R14: ffff88810344ff10 R15: dffffc0000000000
FS:  00007fb58863a6c0(0000) GS:ffff888153bf7000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fb588639f78 CR3: 000000010afa4000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 __refcount_add drivers/media/dvb-core/dvbdev.c:-1 [inline]
 __refcount_inc include/linux/refcount.h:366 [inline]
 refcount_inc include/linux/refcount.h:383 [inline]
 kref_get include/linux/kref.h:45 [inline]
 dvb_device_get drivers/media/dvb-core/dvbdev.c:629 [inline]
 dvb_device_open+0x340/0x370 drivers/media/dvb-core/dvbdev.c:108
 chrdev_open+0x3b8/0x490 fs/char_dev.c:411
 do_dentry_open+0x3f0/0x1120 fs/open.c:947
 vfs_open+0x38/0x2b0 fs/open.c:1052
 do_open fs/namei.c:4700 [inline]
 path_openat+0x21a0/0x29b0 fs/namei.c:4863
 do_file_open+0x19d/0x360 fs/namei.c:4892
 do_sys_openat2+0x9a/0x100 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0xf8/0x130 fs/open.c:1385
 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:0x7fb589738090
Code: 48 89 44 24 20 75 93 44 89 54 24 0c e8 a9 d7 f8 ff 44 8b 54 24 0c 89 da 48 89 ee 41 89 c0 bf 9c ff ff ff b8 01 01 00 00 0f 05 <48> 3d 00 f0 ff ff 77 38 44 89 c7 89 44 24 0c e8 fc d7 f8 ff 8b 44
RSP: 002b:00007fb588639e50 EFLAGS: 00000293 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 0000000000000800 RCX: 00007fb589738090
RDX: 0000000000000800 RSI: 00005c341bf5c060 RDI: 00000000ffffff9c
RBP: 00005c341bf5c060 R08: 0000000000000000 R09: 00007ffece858747
R10: 0000000000000000 R11: 0000000000000293 R12: ffffffffffffff80
R13: 0000000000000000 R14: 00007ffece858650 R15: 00007fb587e3a000
 </TASK>
---[ end trace 0000000000000000 ]---
----------------
Code disassembly (best guess):
   0:	3d ff af bd 04       	cmp    $0x4bdafff,%eax
   5:	67 48 0f b9 3a       	ud1    (%edx),%rdi
   a:	eb 4d                	jmp    0x59
   c:	85 db                	test   %ebx,%ebx
   e:	74 2f                	je     0x3f
  10:	83 fb 01             	cmp    $0x1,%ebx
  13:	75 38                	jne    0x4d
  15:	48 8d 3d f8 af bd 04 	lea    0x4bdaff8(%rip),%rdi        # 0x4bdb014
  1c:	67 48 0f b9 3a       	ud1    (%edx),%rdi
  21:	eb 36                	jmp    0x59
  23:	48 8d 3d fa af bd 04 	lea    0x4bdaffa(%rip),%rdi        # 0x4bdb024
* 2a:	67 48 0f b9 3a       	ud1    (%edx),%rdi <-- trapping instruction
  2f:	eb 28                	jmp    0x59
  31:	48 8d 3d fc af bd 04 	lea    0x4bdaffc(%rip),%rdi        # 0x4bdb034
  38:	67 48 0f b9 3a       	ud1    (%edx),%rdi
  3d:	eb 1a                	jmp    0x59
  3f:	48                   	rex.W
==================================================================