* [BUG] iio: gyro: mpu3050: use-after-free via trigger left on iio_trigger_list after unbind
@ 2026-10-10 20:36 Dingisoul
2026-10-11 17:05 ` Joshua Crofts
0 siblings, 1 reply; 4+ messages in thread
From: Dingisoul @ 2026-10-10 20:36 UTC (permalink / raw)
To: Linus Walleij, Jonathan Cameron
Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio
Hi Kernel Maintainers,
I hit the following report while testing current upstream kernel:
KASAN: slab-use-after-free in mpu3050_drdy_trigger_set_state
on commit: a90ee4305c4a (2026-10-07)
Root cause: mpu3050_trigger_probe() registers the data-ready trigger with
the non-devm iio_trigger_register(), with drvdata pointing at indio_dev:
static int mpu3050_trigger_probe(struct iio_dev *indio_dev, int irq)
{
...
mpu3050->trig->dev.parent = dev;
mpu3050->trig->ops = &mpu3050_trigger_ops;
iio_trigger_set_drvdata(mpu3050->trig, indio_dev);
ret = iio_trigger_register(mpu3050->trig); /* device_add() pins trig; no unregister on remove */
if (ret)
goto err_iio_trigger;
indio_dev->trig = iio_trigger_get(mpu3050->trig);
return 0;
}
mpu3050_common_remove() tears down everything else, but never calls
iio_trigger_unregister() on mpu3050->trig:
void mpu3050_common_remove(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct mpu3050 *mpu3050 = iio_priv(indio_dev);
iio_device_unregister(indio_dev); /* devm frees indio_dev's storage once this returns */
pm_runtime_get_sync(dev);
pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);
if (mpu3050->irq)
free_irq(mpu3050->irq, mpu3050->trig);
iio_triggered_buffer_cleanup(indio_dev);
mpu3050_power_down(mpu3050);
/* trig is never unregistered: still on the global iio_trigger_list */
}
So after unbind, the trigger is still alive and discoverable by name on
the global trigger list, with drvdata still pointing at the now-freed
indio_dev. Any other IIO device with a triggered buffer can select it by
name via trigger/current_trigger and enable its buffer, which reaches
the leaked trigger's ->set_trigger_state() op:
static int mpu3050_drdy_trigger_set_state(struct iio_trigger *trig,
bool enable)
{
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
struct mpu3050 *mpu3050 = iio_priv(indio_dev); /* indio_dev is freed -- UAF read */
...
The reproducer and .config files are here.
https://gist.github.com/dingiso/985aa3eb2786b62f80e2e6780e9af090
I'm happy to test debug patches or provide additional information.
[ 24.039594][ T8117] BUG: KASAN: slab-use-after-free in mpu3050_drdy_trigger_set_state (./include/linux/iio/iio.h:980 drivers/iio/gyro/mpu3050-core.c:957)
[ 24.039630][ T8117] Read of size 8 at addr ffff888030621388 by task repro/8117
[ 24.039634][ T8117]
[ 24.039644][ T8117] Tainted: [E]=UNSIGNED_MODULE
[ 24.039645][ T8117] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 24.039648][ T8117] Call Trace:
[ 24.039650][ T8117] <TASK>
[ 24.039651][ T8117] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[ 24.039663][ T8117] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[ 24.039682][ T8117] kasan_report (mm/kasan/report.c:595)
[ 24.039690][ T8117] mpu3050_drdy_trigger_set_state (./include/linux/iio/iio.h:980 drivers/iio/gyro/mpu3050-core.c:957)
[ 24.039709][ T8117] iio_trigger_attach_poll_func (drivers/iio/industrialio-trigger.c:308)
[ 24.039715][ T8117] __iio_update_buffers (drivers/iio/industrialio-buffer.c:1207 drivers/iio/industrialio-buffer.c:1324)
[ 24.039747][ T8117] enable_store (drivers/iio/industrialio-buffer.c:1414)
[ 24.039765][ T8117] kernfs_fop_write_iter (fs/kernfs/file.c:345)
[ 24.039769][ T8117] vfs_write (fs/read_write.c:595 fs/read_write.c:687)
[ 24.039786][ T8117] ksys_write (fs/read_write.c:739)
[ 24.039795][ T8117] do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84)
[ 24.039799][ T8117] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 24.039803][ T8117] RIP: 0033:0x7fe293245473
[ 24.039807][ T8117] Code: 8b 15 21 2a 0e 00 f7 d8 64 89 02 48 c7 c0 ff ff ff ff eb b7 0f 1f 00 64 8b 04 25 18 00 00 00 85 c0 75 14 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 55 c3 0f 1f 40 00 48 83 ec 28 48 89 54 24 18
All code
========
0: 8b 15 21 2a 0e 00 mov 0xe2a21(%rip),%edx # 0xe2a27
6: f7 d8 neg %eax
8: 64 89 02 mov %eax,%fs:(%rdx)
b: 48 c7 c0 ff ff ff ff mov $0xffffffffffffffff,%rax
12: eb b7 jmp 0xffffffffffffffcb
14: 0f 1f 00 nopl (%rax)
17: 64 8b 04 25 18 00 00 mov %fs:0x18,%eax
1e: 00
1f: 85 c0 test %eax,%eax
21: 75 14 jne 0x37
23: b8 01 00 00 00 mov $0x1,%eax
28: 0f 05 syscall
2a:* 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax <-- trapping instruction
30: 77 55 ja 0x87
32: c3 ret
33: 0f 1f 40 00 nopl 0x0(%rax)
37: 48 83 ec 28 sub $0x28,%rsp
3b: 48 89 54 24 18 mov %rdx,0x18(%rsp)
Code starting with the faulting instruction
===========================================
0: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax
6: 77 55 ja 0x5d
8: c3 ret
9: 0f 1f 40 00 nopl 0x0(%rax)
d: 48 83 ec 28 sub $0x28,%rsp
11: 48 89 54 24 18 mov %rdx,0x18(%rsp)
[ 24.039810][ T8117] RSP: 002b:00007fff7856e628 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
[ 24.039815][ T8117] RAX: ffffffffffffffda RBX: 00007fff7856e6e0 RCX: 00007fe293245473
[ 24.039818][ T8117] RDX: 0000000000000001 RSI: 000056386e819159 RDI: 0000000000000003
[ 24.039820][ T8117] RBP: 0000000000000003 R08: 0000000000004000 R09: 00007fe2932e90c0
[ 24.039822][ T8117] R10: 0000000000000000 R11: 0000000000000246 R12: 000056386e819159
[ 24.039824][ T8117] R13: 00007fff7856ea60 R14: 00007fff7856ea60 R15: 0000563885c1635b
[ 24.039827][ T8117] </TASK>
[ 24.039829][ T8117]
[ 24.039830][ T8117] Freed by task 8117:
[ 24.039832][ T8117] kasan_save_stack (mm/kasan/common.c:57)
[ 24.039835][ T8117] kasan_save_track (mm/kasan/common.c:78)
[ 24.039838][ T8117] kasan_save_free_info (mm/kasan/generic.c:584)
[ 24.039841][ T8117] __kasan_slab_free (mm/kasan/common.c:264 mm/kasan/common.c:296)
[ 24.039844][ T8117] kfree (./include/linux/kasan.h:235 mm/slub.c:2748 mm/slub.c:6509 mm/slub.c:6802)
[ 24.039849][ T8117] device_release (drivers/base/core.c:2645)
[ 24.039854][ T8117] kobject_put (lib/kobject.c:689 lib/kobject.c:720 ./include/linux/kref.h:65 lib/kobject.c:737)
[ 24.039859][ T8117] release_nodes (drivers/base/devres.c:546)
[ 24.039862][ T8117] devres_release_group (drivers/base/devres.c:739)
[ 24.039865][ T8117] i2c_device_remove (drivers/i2c/i2c-core-base.c:637)
[ 24.039871][ T8117] device_release_driver_internal (drivers/base/dd.c:1349 drivers/base/dd.c:1372)
[ 24.039874][ T8117] bus_remove_device (drivers/base/bus.c:664)
[ 24.039878][ T8117] device_del (drivers/base/core.c:3965)
[ 24.039882][ T8117] device_unregister (drivers/base/core.c:4006)
[ 24.039885][ T8117] delete_device_store (drivers/i2c/i2c-core-base.c:1369)
[ 24.039889][ T8117] kernfs_fop_write_iter (fs/kernfs/file.c:345)
[ 24.039892][ T8117] vfs_write (fs/read_write.c:595 fs/read_write.c:687)
[ 24.039895][ T8117] ksys_write (fs/read_write.c:739)
[ 24.039899][ T8117] do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84)
[ 24.039901][ T8117] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
(Full KASAN report, including the trailing slab/page-owner and memory-state info after this point, is in the gist as kasan_report_full.txt. Lines starting with "?" above are stack-scanned frames, not reliable return addresses, and have been omitted here.)
Best,
Dingisoul
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [BUG] iio: gyro: mpu3050: use-after-free via trigger left on iio_trigger_list after unbind
2026-10-10 20:36 [BUG] iio: gyro: mpu3050: use-after-free via trigger left on iio_trigger_list after unbind Dingisoul
@ 2026-10-11 17:05 ` Joshua Crofts
2026-10-12 20:09 ` Dingisoul
0 siblings, 1 reply; 4+ messages in thread
From: Joshua Crofts @ 2026-10-11 17:05 UTC (permalink / raw)
To: Dingisoul
Cc: Linus Walleij, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, linux-iio
On Sat, 10 Oct 2026 16:36:01 -0400
Dingisoul <dingiso.kernel@gmail.com> wrote:
> Hi Kernel Maintainers,
>
> I hit the following report while testing current upstream kernel:
>
> KASAN: slab-use-after-free in mpu3050_drdy_trigger_set_state
>
> on commit: a90ee4305c4a (2026-10-07)
>
> Root cause: mpu3050_trigger_probe() registers the data-ready trigger with
> the non-devm iio_trigger_register(), with drvdata pointing at indio_dev:
>
> static int mpu3050_trigger_probe(struct iio_dev *indio_dev, int irq)
> {
> ...
> mpu3050->trig->dev.parent = dev;
> mpu3050->trig->ops = &mpu3050_trigger_ops;
> iio_trigger_set_drvdata(mpu3050->trig, indio_dev);
>
> ret = iio_trigger_register(mpu3050->trig); /* device_add() pins trig; no unregister on remove */
> if (ret)
> goto err_iio_trigger;
>
> indio_dev->trig = iio_trigger_get(mpu3050->trig);
>
> return 0;
> }
>
> mpu3050_common_remove() tears down everything else, but never calls
> iio_trigger_unregister() on mpu3050->trig:
>
> void mpu3050_common_remove(struct device *dev)
> {
> struct iio_dev *indio_dev = dev_get_drvdata(dev);
> struct mpu3050 *mpu3050 = iio_priv(indio_dev);
>
> iio_device_unregister(indio_dev); /* devm frees indio_dev's storage once this returns */
> pm_runtime_get_sync(dev);
> pm_runtime_put_noidle(dev);
> pm_runtime_disable(dev);
> if (mpu3050->irq)
> free_irq(mpu3050->irq, mpu3050->trig);
> iio_triggered_buffer_cleanup(indio_dev);
> mpu3050_power_down(mpu3050);
> /* trig is never unregistered: still on the global iio_trigger_list */
> }
>
> So after unbind, the trigger is still alive and discoverable by name on
> the global trigger list, with drvdata still pointing at the now-freed
> indio_dev. Any other IIO device with a triggered buffer can select it by
> name via trigger/current_trigger and enable its buffer, which reaches
> the leaked trigger's ->set_trigger_state() op:
>
> static int mpu3050_drdy_trigger_set_state(struct iio_trigger *trig,
> bool enable)
> {
> struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
> struct mpu3050 *mpu3050 = iio_priv(indio_dev); /* indio_dev is freed -- UAF read */
> ...
>
> The reproducer and .config files are here.
> https://gist.github.com/dingiso/985aa3eb2786b62f80e2e6780e9af090
>
> I'm happy to test debug patches or provide additional information.
>
Hi, thanks for the bug report!
I've sent a patch addressing the issue, it would be great if you
could test it.
https://lore.kernel.org/linux-iio/20261011-mpu3050-fix-v1-1-f55b0d138be4@gmail.com/
Thanks.
--
Kind regards,
Joshua Crofts
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [BUG] iio: gyro: mpu3050: use-after-free via trigger left on iio_trigger_list after unbind
2026-10-11 17:05 ` Joshua Crofts
@ 2026-10-12 20:09 ` Dingisoul
2026-10-13 11:30 ` Joshua Crofts
0 siblings, 1 reply; 4+ messages in thread
From: Dingisoul @ 2026-10-12 20:09 UTC (permalink / raw)
To: joshua.crofts1
Cc: andy, dingiso.kernel, dlechner, jic23, linusw, linux-iio, nuno.sa
Hi Joshua, Sashiko,
Sashiko's concern is really about not being able to tell whether the
trigger was ever actually registered before calling
iio_trigger_unregister(). There's a precedent for this exact bug class:
commit 3c5eed894efd9 ("iio: temperature: tmp006: use
devm_iio_trigger_register") hit the same
devm_iio_trigger_alloc()-but-plain-register() mismatch in tmp006, and
fixed it by switching to devm_iio_trigger_register() instead of adding a
manual unregister call.
https://github.com/torvalds/linux/commit/3c5eed894efd9
devm_iio_trigger_register() only installs its cleanup action if
iio_trigger_register() actually succeeds:
int devm_iio_trigger_register(struct device *dev,
struct iio_trigger *trig_info)
{
int ret;
ret = iio_trigger_register(trig_info);
if (ret)
return ret;
return devm_add_action_or_reset(dev, devm_iio_trigger_unreg, trig_info);
}
so it can never unregister a trigger that was never registered -- no
flag/pointer tracking needed.
Suggested fix, in mpu3050_trigger_probe():
- ret = iio_trigger_register(mpu3050->trig);
+ ret = devm_iio_trigger_register(&indio_dev->dev, mpu3050->trig);
(same scope devm_iio_trigger_alloc() already uses a few lines above).
With this, both iio_trigger_unregister() calls in your patch can be
dropped.
I rebuilt with just this one-line change and reran my reproducer:
the trigger registers normally on probe, and after delete_device
it's actually gone instead of left dangling, so the attack just
gets -EINVAL instead of a UAF. No KASAN report, no warnings.
Happy to test further revisions.
Best,
Dingisoul
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [BUG] iio: gyro: mpu3050: use-after-free via trigger left on iio_trigger_list after unbind
2026-10-12 20:09 ` Dingisoul
@ 2026-10-13 11:30 ` Joshua Crofts
0 siblings, 0 replies; 4+ messages in thread
From: Joshua Crofts @ 2026-10-13 11:30 UTC (permalink / raw)
To: Dingisoul; +Cc: andy, dlechner, jic23, linusw, linux-iio, nuno.sa
On Mon, 12 Oct 2026 16:09:04 -0400
Dingisoul <dingiso.kernel@gmail.com> wrote:
> Hi Joshua, Sashiko,
>
> Sashiko's concern is really about not being able to tell whether the
> trigger was ever actually registered before calling
> iio_trigger_unregister(). There's a precedent for this exact bug class:
> commit 3c5eed894efd9 ("iio: temperature: tmp006: use
> devm_iio_trigger_register") hit the same
> devm_iio_trigger_alloc()-but-plain-register() mismatch in tmp006, and
> fixed it by switching to devm_iio_trigger_register() instead of adding a
> manual unregister call.
>
> https://github.com/torvalds/linux/commit/3c5eed894efd9
>
> devm_iio_trigger_register() only installs its cleanup action if
> iio_trigger_register() actually succeeds:
>
> int devm_iio_trigger_register(struct device *dev,
> struct iio_trigger *trig_info)
> {
> int ret;
>
> ret = iio_trigger_register(trig_info);
> if (ret)
> return ret;
>
> return devm_add_action_or_reset(dev, devm_iio_trigger_unreg, trig_info);
> }
>
> so it can never unregister a trigger that was never registered -- no
> flag/pointer tracking needed.
>
> Suggested fix, in mpu3050_trigger_probe():
>
> - ret = iio_trigger_register(mpu3050->trig);
> + ret = devm_iio_trigger_register(&indio_dev->dev, mpu3050->trig);
Ideally you shouldn't mix devm_* and non-devm_* function, this can cause headaches
down the road. Either we do the manual dance or we convert the entire driver to
devm_*. If you check the file in the commit you referenced above, you'll notice
that all the other functions are devm_*.
Bartosz Golaszewski had a talk on this at OSS Prague (recording still isn't
available, so I'm attaching the presentation instead).
https://hosted-files.sched.co/osselceu2026/0c/OSS%202026%20Prague.pdf
--
Kind regards,
Joshua Crofts
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-13 11:30 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-10 20:36 [BUG] iio: gyro: mpu3050: use-after-free via trigger left on iio_trigger_list after unbind Dingisoul
2026-10-11 17:05 ` Joshua Crofts
2026-10-12 20:09 ` Dingisoul
2026-10-13 11:30 ` Joshua Crofts
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox