From: Max Kellermann Date: Mon, 4 Jul 2016 12:08:45 +0000 (+0200) Subject: [media] dvb-core/en50221: use kref to manage struct dvb_ca_private X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=da677fe14364;p=linux-beck.git [media] dvb-core/en50221: use kref to manage struct dvb_ca_private Don't free the object until the file handle has been closed. Fixes use-after-free bug which occurs when I disconnect my DVB-S received while VDR is running. This is a crash dump of such a use-after-free: general protection fault: 0000 [#1] SMP CPU: 0 PID: 2541 Comm: CI adapter on d Not tainted 4.7.0-rc1-hosting+ #49 Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 task: ffff880027d7ce00 ti: ffff88003d8f8000 task.ti: ffff88003d8f8000 RIP: 0010:[] [] dvb_ca_en50221_io_read_condition.isra.7+0x6f/0x150 RSP: 0018:ffff88003d8fba98 EFLAGS: 00010206 RAX: 0000000059534255 RBX: 000000753d470f90 RCX: ffff88003c74d181 RDX: 00000001bea04ba9 RSI: ffff88003d8fbaf4 RDI: 3a3030a56d763fc0 RBP: ffff88003d8fbae0 R08: ffff88003c74d180 R09: 0000000000000000 R10: 0000000000000001 R11: 0000000000000000 R12: ffff88003c480e00 R13: 00000000ffffffff R14: 0000000059534255 R15: 0000000000000000 FS: 00007fb4209b4700(0000) GS:ffff88003fc00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f06445f4078 CR3: 000000003c55b000 CR4: 00000000000006b0 Stack: ffff88003d8fbaf4 000000003c2170c0 0000000000004000 0000000000000000 ffff88003c480e00 ffff88003d8fbc80 ffff88003c74d180 ffff88003d8fbb8c 0000000000000000 ffff88003d8fbb10 ffffffff812f3e37 ffff88003d8fbb00 Call Trace: [] dvb_ca_en50221_io_poll+0x37/0xa0 [] do_sys_poll+0x2db/0x520 This is a backtrace of the kernel attempting to lock a freed mutex: #0 0xffffffff81083d40 in rep_nop () at ./arch/x86/include/asm/processor.h:569 #1 cpu_relax () at ./arch/x86/include/asm/processor.h:574 #2 virt_spin_lock (lock=) at ./arch/x86/include/asm/qspinlock.h:57 #3 native_queued_spin_lock_slowpath (lock=0xffff88003c480e90, val=761492029) at kernel/locking/qspinlock.c:304 #4 0xffffffff810d1a06 in pv_queued_spin_lock_slowpath (val=, lock=) at ./arch/x86/include/asm/paravirt.h:669 #5 queued_spin_lock_slowpath (val=, lock=) at ./arch/x86/include/asm/qspinlock.h:28 #6 queued_spin_lock (lock=) at include/asm-generic/qspinlock.h:107 #7 __mutex_lock_common (use_ww_ctx=, ww_ctx=, ip=, nest_lock=, subclass=, state=, lock=) at kernel/locking/mutex.c:526 #8 mutex_lock_interruptible_nested (lock=0xffff88003c480e88, subclass=) at kernel/locking/mutex.c:647 #9 0xffffffff812f49fe in dvb_ca_en50221_io_do_ioctl (file=, cmd=761492029, parg=0x1 ) at drivers/media/dvb-core/dvb_ca_en50221.c:1210 #10 0xffffffff812ee660 in dvb_usercopy (file=, cmd=761492029, arg=, func=) at drivers/media/dvb-core/dvbdev.c:883 #11 0xffffffff812f3410 in dvb_ca_en50221_io_ioctl (file=, cmd=, arg=) at drivers/media/dvb-core/dvb_ca_en50221.c:1284 #12 0xffffffff8112eddd in vfs_ioctl (arg=, cmd=, filp=) at fs/ioctl.c:43 #13 do_vfs_ioctl (filp=0xffff88003c480e90, fd=, cmd=, arg=) at fs/ioctl.c:674 #14 0xffffffff8112f30c in SYSC_ioctl (arg=, cmd=, fd=) at fs/ioctl.c:689 #15 SyS_ioctl (fd=6, cmd=2148298626, arg=140734533693696) at fs/ioctl.c:680 #16 0xffffffff8103feb2 in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:207 Signed-off-by: Max Kellermann Signed-off-by: Mauro Carvalho Chehab --- diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c index b1e3a26b1431..b5b5b195ea7f 100644 --- a/drivers/media/dvb-core/dvb_ca_en50221.c +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -123,6 +123,7 @@ struct dvb_ca_slot { /* Private CA-interface information */ struct dvb_ca_private { + struct kref refcount; /* pointer back to the public data structure */ struct dvb_ca_en50221 *pub; @@ -173,6 +174,22 @@ static void dvb_ca_private_free(struct dvb_ca_private *ca) kfree(ca); } +static void dvb_ca_private_release(struct kref *ref) +{ + struct dvb_ca_private *ca = container_of(ref, struct dvb_ca_private, refcount); + dvb_ca_private_free(ca); +} + +static void dvb_ca_private_get(struct dvb_ca_private *ca) +{ + kref_get(&ca->refcount); +} + +static void dvb_ca_private_put(struct dvb_ca_private *ca) +{ + kref_put(&ca->refcount, dvb_ca_private_release); +} + static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca); static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount); static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount); @@ -1570,6 +1587,8 @@ static int dvb_ca_en50221_io_open(struct inode *inode, struct file *file) dvb_ca_en50221_thread_update_delay(ca); dvb_ca_en50221_thread_wakeup(ca); + dvb_ca_private_get(ca); + return 0; } @@ -1598,6 +1617,8 @@ static int dvb_ca_en50221_io_release(struct inode *inode, struct file *file) module_put(ca->pub->owner); + dvb_ca_private_put(ca); + return err; } @@ -1693,6 +1714,7 @@ int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter, ret = -ENOMEM; goto exit; } + kref_init(&ca->refcount); ca->pub = pubca; ca->flags = flags; ca->slot_count = slot_count; @@ -1772,6 +1794,6 @@ void dvb_ca_en50221_release(struct dvb_ca_en50221 *pubca) for (i = 0; i < ca->slot_count; i++) { dvb_ca_en50221_slot_shutdown(ca, i); } - dvb_ca_private_free(ca); + dvb_ca_private_put(ca); pubca->private = NULL; }