[BUG] WARNING: refcount bug in dvb_device_open

Jaeyoung Chung <[email protected]>
Newsgroups org.kernel.vger.linux-media,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
Reported-by: Jaeyoung Chung <[email protected]>

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
==================================================================
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.