* [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
@ 2026-06-14 19:48 Shuangpeng Bai
2026-06-15 4:03 ` Greg KH
0 siblings, 1 reply; 22+ messages in thread
From: Shuangpeng Bai @ 2026-06-14 19:48 UTC (permalink / raw)
To: vaibhavgupta40, jens.taprogge, gregkh, kees, industrypack-devel,
linux-kernel
Hi Kernel Maintainers,
I hit the following report while testing current upstream kernel:
KASAN: slab-use-after-free in ipoctal_write_tty
on commit: e8c2f9fdadee7cbc75134dc463c1e0d856d6e5c7 (May 25 2026)
The reproducer and .config files are here.
https://gist.github.com/shuangpengbai/bc71ccd7ce1454abae45897fdde6813b
I'm happy to test debug patches or provide additional information.
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
[ 109.226970][ T8370] BUG: KASAN: slab-use-after-free in ipoctal_write_tty (drivers/ipack/devices/ipoctal.c:446 drivers/ipack/devices/ipoctal.c:465)
[ 109.227786][ T8370] Read of size 4 at addr ffff88810539e040 by task ipoctal_fd_afte/8370
[ 109.228633][ T8370]
[ 109.228884][ T8370] 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
[ 109.228887][ T8370] Call Trace:
[ 109.228890][ T8370] <TASK>
[ 109.228892][ T8370] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[ 109.228898][ T8370] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[ 109.228916][ T8370] kasan_report (mm/kasan/report.c:595)
[ 109.228931][ T8370] ipoctal_write_tty (drivers/ipack/devices/ipoctal.c:446 drivers/ipack/devices/ipoctal.c:465)
[ 109.228936][ T8370] n_tty_write (drivers/tty/n_tty.c:2388)
[ 109.228977][ T8370] file_tty_write (drivers/tty/tty_io.c:1006 drivers/tty/tty_io.c:1081)
[ 109.228982][ T8370] vfs_write (fs/read_write.c:595 fs/read_write.c:688)
[ 109.229000][ T8370] ksys_write (fs/read_write.c:740)
[ 109.229006][ T8370] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 109.229009][ T8370] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 109.229013][ T8370] RIP: 0033:0x7f4626f1a473
[ 109.229017][ T8370] 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
[ 109.229020][ T8370] RSP: 002b:00007ffe7ca57188 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
[ 109.229025][ T8370] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00007f4626f1a473
[ 109.229028][ T8370] RDX: 0000000000001000 RSI: 00007ffe7ca571a0 RDI: 0000000000000003
[ 109.229031][ T8370] RBP: 0000000000000003 R08: 0000000000000000 R09: 00007f4626fbe0c0
[ 109.229033][ T8370] R10: fffffffffffff5fb R11: 0000000000000246 R12: 0000000000000000
[ 109.229035][ T8370] R13: 00007ffe7ca571a0 R14: 000000000000000f R15: 00007ffe7ca5719c
[ 109.229039][ T8370] </TASK>
[ 109.229041][ T8370]
[ 109.253422][ T8370] Freed by task 8370 on cpu 0 at 106.552583s:
[ 109.254047][ T8370] kasan_save_track (mm/kasan/common.c:57 mm/kasan/common.c:78)
[ 109.254541][ T8370] kasan_save_free_info (mm/kasan/generic.c:584)
[ 109.255058][ T8370] __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
[ 109.255556][ T8370] kfree (include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
[ 109.255962][ T8370] device_release_driver_internal (drivers/base/dd.c:619 drivers/base/dd.c:1352 drivers/base/dd.c:1375)
[ 109.256595][ T8370] bus_remove_device (drivers/base/bus.c:657)
[ 109.257105][ T8370] device_del (drivers/base/core.c:3895)
[ 109.257564][ T8370] ipack_unregister_bus_member (drivers/ipack/ipack.c:469 drivers/ipack/ipack.c:231)
[ 109.258138][ T8370] bus_for_each_dev (drivers/base/bus.c:383)
[ 109.258648][ T8370] ipack_bus_unregister (drivers/ipack/ipack.c:238)
[ 109.259164][ T8370] tpci200_pci_remove (drivers/ipack/carriers/tpci200.c:611 drivers/ipack/carriers/tpci200.c:628)
[ 109.259678][ T8370] pci_device_remove (drivers/pci/pci-driver.c:512)
[ 109.260181][ T8370] device_release_driver_internal (drivers/base/dd.c:619 drivers/base/dd.c:1352 drivers/base/dd.c:1375)
[ 109.260812][ T8370] unbind_store (drivers/base/bus.c:244)
[ 109.261292][ T8370] kernfs_fop_write_iter (fs/kernfs/file.c:352)
[ 109.261840][ T8370] vfs_write (fs/read_write.c:595 fs/read_write.c:688)
[ 109.262286][ T8370] ksys_write (fs/read_write.c:740)
[ 109.262736][ T8370] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 109.263216][ T8370] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 109.263835][ T8370]
[ 109.264082][ T8370] The buggy address belongs to the object at ffff88810539e000
[ 109.264082][ T8370] which belongs to the cache kmalloc-4k of size 4096
[ 109.265511][ T8370] The buggy address is located 64 bytes inside of
[ 109.265511][ T8370] freed 4096-byte region [ffff88810539e000, ffff88810539f000)
[ 109.266919][ T8370]
Best,
Shuangpeng
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-14 19:48 [BUG] KASAN: slab-use-after-free in ipoctal_write_tty Shuangpeng Bai
@ 2026-06-15 4:03 ` Greg KH
2026-06-15 20:33 ` Shuangpeng
0 siblings, 1 reply; 22+ messages in thread
From: Greg KH @ 2026-06-15 4:03 UTC (permalink / raw)
To: Shuangpeng Bai
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
> Hi Kernel Maintainers,
>
> I hit the following report while testing current upstream kernel:
>
> KASAN: slab-use-after-free in ipoctal_write_tty
Cool, do you have this hardware, or is this only virtual testing?
If virtual, are you sure that the hardware is being emulated properly?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-15 4:03 ` Greg KH
@ 2026-06-15 20:33 ` Shuangpeng
2026-06-15 20:49 ` Greg KH
0 siblings, 1 reply; 22+ messages in thread
From: Shuangpeng @ 2026-06-15 20:33 UTC (permalink / raw)
To: Greg KH
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
> On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
>> Hi Kernel Maintainers,
>>
>> I hit the following report while testing current upstream kernel:
>>
>> KASAN: slab-use-after-free in ipoctal_write_tty
>
> Cool, do you have this hardware, or is this only virtual testing?
No, I do not have the physical hardware. This was reproduced with
unmodified QEMU using its existing TPCI200/IP-Octal emulation.
>
> If virtual, are you sure that the hardware is being emulated properly?
I understand this is not the same as testing on real hardware. However,
my current understanding is that the crash is triggered after a
successful probe through the normal sysfs unbind/remove path while the
ipoctal tty fd is still open. The failing path does not seem to rely on
device-specific emulation details after probe, but rather on the
lifetime of the tty/device state during removal.
Please let me know if I am missing anything here. I would also
appreciate any suggestions on what I could check to better evaluate
whether the emulation is appropriate for this report.
Best,
Shuangpeng
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-15 20:33 ` Shuangpeng
@ 2026-06-15 20:49 ` Greg KH
2026-06-16 0:11 ` Shuangpeng
0 siblings, 1 reply; 22+ messages in thread
From: Greg KH @ 2026-06-15 20:49 UTC (permalink / raw)
To: Shuangpeng
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
On Mon, Jun 15, 2026 at 04:33:09PM -0400, Shuangpeng wrote:
>
>
> > On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
> >
> > On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
> >> Hi Kernel Maintainers,
> >>
> >> I hit the following report while testing current upstream kernel:
> >>
> >> KASAN: slab-use-after-free in ipoctal_write_tty
> >
> > Cool, do you have this hardware, or is this only virtual testing?
>
> No, I do not have the physical hardware. This was reproduced with
> unmodified QEMU using its existing TPCI200/IP-Octal emulation.
>
> >
> > If virtual, are you sure that the hardware is being emulated properly?
>
>
> I understand this is not the same as testing on real hardware. However,
> my current understanding is that the crash is triggered after a
> successful probe through the normal sysfs unbind/remove path while the
> ipoctal tty fd is still open. The failing path does not seem to rely on
> device-specific emulation details after probe, but rather on the
> lifetime of the tty/device state during removal.
What specific sysfs unbind path? That's only for root and for testing
kernel development, it's not a normal thing that a user does at all,
right?
> Please let me know if I am missing anything here. I would also
> appreciate any suggestions on what I could check to better evaluate
> whether the emulation is appropriate for this report.
What exactly are you trying to test?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-15 20:49 ` Greg KH
@ 2026-06-16 0:11 ` Shuangpeng
2026-06-16 2:46 ` Greg KH
0 siblings, 1 reply; 22+ messages in thread
From: Shuangpeng @ 2026-06-16 0:11 UTC (permalink / raw)
To: Greg KH
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
> On Jun 15, 2026, at 16:49, Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Mon, Jun 15, 2026 at 04:33:09PM -0400, Shuangpeng wrote:
>>
>>
>>> On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>
>>> On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
>>>> Hi Kernel Maintainers,
>>>>
>>>> I hit the following report while testing current upstream kernel:
>>>>
>>>> KASAN: slab-use-after-free in ipoctal_write_tty
>>>
>>> Cool, do you have this hardware, or is this only virtual testing?
>>
>> No, I do not have the physical hardware. This was reproduced with
>> unmodified QEMU using its existing TPCI200/IP-Octal emulation.
>>
>>>
>>> If virtual, are you sure that the hardware is being emulated properly?
>>
>>
>> I understand this is not the same as testing on real hardware. However,
>> my current understanding is that the crash is triggered after a
>> successful probe through the normal sysfs unbind/remove path while the
>> ipoctal tty fd is still open. The failing path does not seem to rely on
>> device-specific emulation details after probe, but rather on the
>> lifetime of the tty/device state during removal.
>
> What specific sysfs unbind path? That's only for root and for testing
> kernel development, it's not a normal thing that a user does at all,
> right?
>
The sysfs path used by the reproducer is:
/sys/bus/pci/drivers/tpci200/unbind
So yes, this is a root-only kernel testing/development path, not a
normal unprivileged user path.
>> Please let me know if I am missing anything here. I would also
>> appreciate any suggestions on what I could check to better evaluate
>> whether the emulation is appropriate for this report.
>
> What exactly are you trying to test?
I was trying to test whether the driver handles open ipoctal tty file
descriptors safely when the backing TPCI200/IPack device is removed.
Thanks,
Shuangpeng
>
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-16 0:11 ` Shuangpeng
@ 2026-06-16 2:46 ` Greg KH
2026-06-16 16:26 ` Shuangpeng
2026-07-01 2:11 ` [BUG] KASAN: slab-use-after-free in ipoctal_write_tty Pei Xiao
0 siblings, 2 replies; 22+ messages in thread
From: Greg KH @ 2026-06-16 2:46 UTC (permalink / raw)
To: Shuangpeng
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
On Mon, Jun 15, 2026 at 08:11:49PM -0400, Shuangpeng wrote:
>
>
> > On Jun 15, 2026, at 16:49, Greg KH <gregkh@linuxfoundation.org> wrote:
> >
> > On Mon, Jun 15, 2026 at 04:33:09PM -0400, Shuangpeng wrote:
> >>
> >>
> >>> On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
> >>>
> >>> On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
> >>>> Hi Kernel Maintainers,
> >>>>
> >>>> I hit the following report while testing current upstream kernel:
> >>>>
> >>>> KASAN: slab-use-after-free in ipoctal_write_tty
> >>>
> >>> Cool, do you have this hardware, or is this only virtual testing?
> >>
> >> No, I do not have the physical hardware. This was reproduced with
> >> unmodified QEMU using its existing TPCI200/IP-Octal emulation.
> >>
> >>>
> >>> If virtual, are you sure that the hardware is being emulated properly?
> >>
> >>
> >> I understand this is not the same as testing on real hardware. However,
> >> my current understanding is that the crash is triggered after a
> >> successful probe through the normal sysfs unbind/remove path while the
> >> ipoctal tty fd is still open. The failing path does not seem to rely on
> >> device-specific emulation details after probe, but rather on the
> >> lifetime of the tty/device state during removal.
> >
> > What specific sysfs unbind path? That's only for root and for testing
> > kernel development, it's not a normal thing that a user does at all,
> > right?
> >
>
> The sysfs path used by the reproducer is:
>
> /sys/bus/pci/drivers/tpci200/unbind
>
> So yes, this is a root-only kernel testing/development path, not a
> normal unprivileged user path.
>
> >> Please let me know if I am missing anything here. I would also
> >> appreciate any suggestions on what I could check to better evaluate
> >> whether the emulation is appropriate for this report.
> >
> > What exactly are you trying to test?
>
> I was trying to test whether the driver handles open ipoctal tty file
> descriptors safely when the backing TPCI200/IPack device is removed.
As you found, it doesn't :)
See the discussions about device unbind and misc/char device nodes on
the mailing lists for many messages about this and potential ways to
resolve it. As it's not a real issue for drivers like this, it's a very
low priority for other people to resolve, but we will always gladly
review patches from others.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-16 2:46 ` Greg KH
@ 2026-06-16 16:26 ` Shuangpeng
2026-06-23 7:46 ` Pei Xiao
2026-07-01 2:11 ` [BUG] KASAN: slab-use-after-free in ipoctal_write_tty Pei Xiao
1 sibling, 1 reply; 22+ messages in thread
From: Shuangpeng @ 2026-06-16 16:26 UTC (permalink / raw)
To: Greg KH
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
> On Jun 15, 2026, at 22:46, Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Mon, Jun 15, 2026 at 08:11:49PM -0400, Shuangpeng wrote:
>>
>>
>>> On Jun 15, 2026, at 16:49, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>
>>> On Mon, Jun 15, 2026 at 04:33:09PM -0400, Shuangpeng wrote:
>>>>
>>>>
>>>>> On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>>>
>>>>> On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
>>>>>> Hi Kernel Maintainers,
>>>>>>
>>>>>> I hit the following report while testing current upstream kernel:
>>>>>>
>>>>>> KASAN: slab-use-after-free in ipoctal_write_tty
>>>>>
>>>>> Cool, do you have this hardware, or is this only virtual testing?
>>>>
>>>> No, I do not have the physical hardware. This was reproduced with
>>>> unmodified QEMU using its existing TPCI200/IP-Octal emulation.
>>>>
>>>>>
>>>>> If virtual, are you sure that the hardware is being emulated properly?
>>>>
>>>>
>>>> I understand this is not the same as testing on real hardware. However,
>>>> my current understanding is that the crash is triggered after a
>>>> successful probe through the normal sysfs unbind/remove path while the
>>>> ipoctal tty fd is still open. The failing path does not seem to rely on
>>>> device-specific emulation details after probe, but rather on the
>>>> lifetime of the tty/device state during removal.
>>>
>>> What specific sysfs unbind path? That's only for root and for testing
>>> kernel development, it's not a normal thing that a user does at all,
>>> right?
>>>
>>
>> The sysfs path used by the reproducer is:
>>
>> /sys/bus/pci/drivers/tpci200/unbind
>>
>> So yes, this is a root-only kernel testing/development path, not a
>> normal unprivileged user path.
>>
>>>> Please let me know if I am missing anything here. I would also
>>>> appreciate any suggestions on what I could check to better evaluate
>>>> whether the emulation is appropriate for this report.
>>>
>>> What exactly are you trying to test?
>>
>> I was trying to test whether the driver handles open ipoctal tty file
>> descriptors safely when the backing TPCI200/IPack device is removed.
>
> As you found, it doesn't :)
>
> See the discussions about device unbind and misc/char device nodes on
> the mailing lists for many messages about this and potential ways to
> resolve it. As it's not a real issue for drivers like this, it's a very
> low priority for other people to resolve, but we will always gladly
> review patches from others.
>
Thanks for the explanation and the pointers.
I will read through the related discussions to better understand how opened
file descriptors are expected to be handled when the underlying device or
driver is removed. This is very helpful.
Thanks,
Shuangpeng
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-16 16:26 ` Shuangpeng
@ 2026-06-23 7:46 ` Pei Xiao
2026-06-23 7:50 ` [RFC] ipack: ipoctal: fix use-after-free on remove Pei Xiao
0 siblings, 1 reply; 22+ messages in thread
From: Pei Xiao @ 2026-06-23 7:46 UTC (permalink / raw)
To: Shuangpeng; +Cc: linux-kernel
在 2026/6/17 00:26, Shuangpeng 写道:
>
>> On Jun 15, 2026, at 22:46, Greg KH <gregkh@linuxfoundation.org> wrote:
>>
>> On Mon, Jun 15, 2026 at 08:11:49PM -0400, Shuangpeng wrote:
>>>
>>>> On Jun 15, 2026, at 16:49, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>>
>>>> On Mon, Jun 15, 2026 at 04:33:09PM -0400, Shuangpeng wrote:
>>>>>
>>>>>> On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>>>>
>>>>>> On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
>>>>>>> Hi Kernel Maintainers,
>>>>>>>
>>>>>>> I hit the following report while testing current upstream kernel:
>>>>>>>
>>>>>>> KASAN: slab-use-after-free in ipoctal_write_tty
>>>>>> Cool, do you have this hardware, or is this only virtual testing?
>>>>> No, I do not have the physical hardware. This was reproduced with
>>>>> unmodified QEMU using its existing TPCI200/IP-Octal emulation.
>>>>>
>>>>>> If virtual, are you sure that the hardware is being emulated properly?
>>>>>
>>>>> I understand this is not the same as testing on real hardware. However,
>>>>> my current understanding is that the crash is triggered after a
>>>>> successful probe through the normal sysfs unbind/remove path while the
>>>>> ipoctal tty fd is still open. The failing path does not seem to rely on
>>>>> device-specific emulation details after probe, but rather on the
>>>>> lifetime of the tty/device state during removal.
>>>> What specific sysfs unbind path? That's only for root and for testing
>>>> kernel development, it's not a normal thing that a user does at all,
>>>> right?
>>>>
>>> The sysfs path used by the reproducer is:
>>>
>>> /sys/bus/pci/drivers/tpci200/unbind
>>>
>>> So yes, this is a root-only kernel testing/development path, not a
>>> normal unprivileged user path.
>>>
>>>>> Please let me know if I am missing anything here. I would also
>>>>> appreciate any suggestions on what I could check to better evaluate
>>>>> whether the emulation is appropriate for this report.
>>>> What exactly are you trying to test?
>>> I was trying to test whether the driver handles open ipoctal tty file
>>> descriptors safely when the backing TPCI200/IPack device is removed.
>> As you found, it doesn't :)
>>
>> See the discussions about device unbind and misc/char device nodes on
>> the mailing lists for many messages about this and potential ways to
>> resolve it. As it's not a real issue for drivers like this, it's a very
>> low priority for other people to resolve, but we will always gladly
>> review patches from others.
>>
> Thanks for the explanation and the pointers.
>
> I will read through the related discussions to better understand how opened
> file descriptors are expected to be handled when the underlying device or
> driver is removed. This is very helpful.
>
> Thanks,
> Shuangpeng
Hi Shuangppeng,
Are you working on this bug? If not, could you help test this patch?
If you're already handling this, I apologize for the interruption.
Thanks!
>> thanks,
>>
>> greg k-h
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC] ipack: ipoctal: fix use-after-free on remove
2026-06-23 7:46 ` Pei Xiao
@ 2026-06-23 7:50 ` Pei Xiao
2026-06-24 19:47 ` Shuangpeng
0 siblings, 1 reply; 22+ messages in thread
From: Pei Xiao @ 2026-06-23 7:50 UTC (permalink / raw)
To: xiaopei01; +Cc: linux-kernel, shuangpeng.kernel
A use-after-free occurs when the device is removed while a tty
session is still active. The remove callback frees the ipoctal
structure via kfree() while ipoctal_write_tty() and other tty ops
may still access it.
Fix this by introducing kref-based lifetime management for the
ipoctal structure. A kref is taken in ipoctal_install() when a tty is
initialized, and released in ipoctal_cleanup() when the tty is finally
destroyed. The remove callback replaces direct kfree() with kref_put(),
ensuring the memory is only freed after all tty references have been
released.
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
---
drivers/ipack/devices/ipoctal.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
index 1bbefc6de708..079bda93ad5a 100644
--- a/drivers/ipack/devices/ipoctal.c
+++ b/drivers/ipack/devices/ipoctal.c
@@ -10,6 +10,7 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/serial.h>
@@ -25,6 +26,8 @@
static const struct tty_operations ipoctal_fops;
+static void ipoctal_release(struct kref *kref);
+
struct ipoctal_channel {
struct ipoctal_stats stats;
unsigned int nb_bytes;
@@ -49,6 +52,7 @@ struct ipoctal {
struct tty_driver *tty_drv;
u8 __iomem *mem8_space;
u8 __iomem *int_space;
+ struct kref kref;
};
static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
@@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
if (res)
goto err_put_carrier;
+ kref_get(&ipoctal->kref);
tty->driver_data = channel;
return 0;
@@ -666,6 +671,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
/* release the carrier driver */
ipack_put_carrier(ipoctal->dev);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static const struct tty_operations ipoctal_fops = {
@@ -683,6 +689,13 @@ static const struct tty_operations ipoctal_fops = {
.cleanup = ipoctal_cleanup,
};
+static void ipoctal_release(struct kref *kref)
+{
+ struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
+
+ kfree(ipoctal);
+}
+
static int ipoctal_probe(struct ipack_device *dev)
{
int res;
@@ -692,6 +705,8 @@ static int ipoctal_probe(struct ipack_device *dev)
if (ipoctal == NULL)
return -ENOMEM;
+ kref_init(&ipoctal->kref);
+
ipoctal->dev = dev;
res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
if (res)
@@ -701,7 +716,7 @@ static int ipoctal_probe(struct ipack_device *dev)
return 0;
out_uninst:
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
return res;
}
@@ -725,7 +740,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
tty_unregister_driver(ipoctal->tty_drv);
kfree(ipoctal->tty_drv->name);
tty_driver_kref_put(ipoctal->tty_drv);
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static void ipoctal_remove(struct ipack_device *idev)
--
2.25.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC] ipack: ipoctal: fix use-after-free on remove
2026-06-23 7:50 ` [RFC] ipack: ipoctal: fix use-after-free on remove Pei Xiao
@ 2026-06-24 19:47 ` Shuangpeng
2026-06-25 1:31 ` Pei Xiao
2026-06-25 1:33 ` [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
0 siblings, 2 replies; 22+ messages in thread
From: Shuangpeng @ 2026-06-24 19:47 UTC (permalink / raw)
To: Pei Xiao; +Cc: linux-kernel
Hi Pei,
Thanks for working on this!
I applied the patch to commit:
390d73adf896bf4883c7d3bcd13c1b53d64351e3 (Jun 19 2026)
and reran the same reproducer. The original slab-use-after-free report did
not show up in the same form, but the reproducer still triggers another
KASAN report in ipoctal_write_tty():
[ 73.728267] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN PTI
[ 73.730941] KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
[ 73.731780] CPU: 0 UID: 0 PID: 8289 Comm: ipoctal_fd_afte Not tainted 7.1.0-10997-g390d73adf896-dirty #33 PREEMPT(full)
[ 73.734223] RIP: 0010:ipoctal_write_tty (drivers/ipack/devices/ipoctal.c:453 drivers/ipack/devices/ipoctal.c:470)
Call Trace:
[ 73.746486] n_tty_write (drivers/tty/n_tty.c:2388)
[ 73.752256] file_tty_write (drivers/tty/tty_io.c:1006 drivers/tty/tty_io.c:1081)
[ 73.752746] vfs_write (fs/read_write.c:595 fs/read_write.c:687)
[ 73.754695] ksys_write (fs/read_write.c:739)
[ 73.756837] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 73.757309] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
So the same open-tty-fd-after-tpci200-unbind reproducer still crashes the
kernel after this patch.
Best,
Shuangpeng
> On Jun 23, 2026, at 03:50, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>
> A use-after-free occurs when the device is removed while a tty
> session is still active. The remove callback frees the ipoctal
> structure via kfree() while ipoctal_write_tty() and other tty ops
> may still access it.
>
> Fix this by introducing kref-based lifetime management for the
> ipoctal structure. A kref is taken in ipoctal_install() when a tty is
> initialized, and released in ipoctal_cleanup() when the tty is finally
> destroyed. The remove callback replaces direct kfree() with kref_put(),
> ensuring the memory is only freed after all tty references have been
> released.
>
> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
> ---
> drivers/ipack/devices/ipoctal.c | 19 +++++++++++++++++--
> 1 file changed, 17 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
> index 1bbefc6de708..079bda93ad5a 100644
> --- a/drivers/ipack/devices/ipoctal.c
> +++ b/drivers/ipack/devices/ipoctal.c
> @@ -10,6 +10,7 @@
> #include <linux/device.h>
> #include <linux/module.h>
> #include <linux/interrupt.h>
> +#include <linux/kref.h>
> #include <linux/sched.h>
> #include <linux/tty.h>
> #include <linux/serial.h>
> @@ -25,6 +26,8 @@
>
> static const struct tty_operations ipoctal_fops;
>
> +static void ipoctal_release(struct kref *kref);
> +
> struct ipoctal_channel {
> struct ipoctal_stats stats;
> unsigned int nb_bytes;
> @@ -49,6 +52,7 @@ struct ipoctal {
> struct tty_driver *tty_drv;
> u8 __iomem *mem8_space;
> u8 __iomem *int_space;
> + struct kref kref;
> };
>
> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
> @@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
> if (res)
> goto err_put_carrier;
>
> + kref_get(&ipoctal->kref);
> tty->driver_data = channel;
>
> return 0;
> @@ -666,6 +671,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>
> /* release the carrier driver */
> ipack_put_carrier(ipoctal->dev);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static const struct tty_operations ipoctal_fops = {
> @@ -683,6 +689,13 @@ static const struct tty_operations ipoctal_fops = {
> .cleanup = ipoctal_cleanup,
> };
>
> +static void ipoctal_release(struct kref *kref)
> +{
> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
> +
> + kfree(ipoctal);
> +}
> +
> static int ipoctal_probe(struct ipack_device *dev)
> {
> int res;
> @@ -692,6 +705,8 @@ static int ipoctal_probe(struct ipack_device *dev)
> if (ipoctal == NULL)
> return -ENOMEM;
>
> + kref_init(&ipoctal->kref);
> +
> ipoctal->dev = dev;
> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
> if (res)
> @@ -701,7 +716,7 @@ static int ipoctal_probe(struct ipack_device *dev)
> return 0;
>
> out_uninst:
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> return res;
> }
>
> @@ -725,7 +740,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
> tty_unregister_driver(ipoctal->tty_drv);
> kfree(ipoctal->tty_drv->name);
> tty_driver_kref_put(ipoctal->tty_drv);
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static void ipoctal_remove(struct ipack_device *idev)
> --
> 2.25.1
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC] ipack: ipoctal: fix use-after-free on remove
2026-06-24 19:47 ` Shuangpeng
@ 2026-06-25 1:31 ` Pei Xiao
2026-06-25 1:33 ` [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
1 sibling, 0 replies; 22+ messages in thread
From: Pei Xiao @ 2026-06-25 1:31 UTC (permalink / raw)
To: Shuangpeng; +Cc: linux-kernel
在 2026/6/25 03:47, Shuangpeng 写道:
> Hi Pei,
>
> Thanks for working on this!
>
> I applied the patch to commit:
>
> 390d73adf896bf4883c7d3bcd13c1b53d64351e3 (Jun 19 2026)
>
> and reran the same reproducer. The original slab-use-after-free report did
> not show up in the same form, but the reproducer still triggers another
> KASAN r
> eport in ipoctal_write_tty():
Hi shuangpeng,
I really appreciate that you've spent so much time helping me test this
patch. I thought it would just go unnoticed.
/I've made another revision. Could you please test it again?/
Thanks!
> [ 73.728267] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN PTI
> [ 73.730941] KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
> [ 73.731780] CPU: 0 UID: 0 PID: 8289 Comm: ipoctal_fd_afte Not tainted 7.1.0-10997-g390d73adf896-dirty #33 PREEMPT(full)
> [ 73.734223] RIP: 0010:ipoctal_write_tty (drivers/ipack/devices/ipoctal.c:453 drivers/ipack/devices/ipoctal.c:470)
>
> Call Trace:
> [ 73.746486] n_tty_write (drivers/tty/n_tty.c:2388)
> [ 73.752256] file_tty_write (drivers/tty/tty_io.c:1006 drivers/tty/tty_io.c:1081)
> [ 73.752746] vfs_write (fs/read_write.c:595 fs/read_write.c:687)
> [ 73.754695] ksys_write (fs/read_write.c:739)
> [ 73.756837] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [ 73.757309] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
>
> So the same open-tty-fd-after-tpci200-unbind reproducer still crashes the
> kernel after this patch.
>
> Best,
> Shuangpeng
>
>
>
>> On Jun 23, 2026, at 03:50, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>>
>> A use-after-free occurs when the device is removed while a tty
>> session is still active. The remove callback frees the ipoctal
>> structure via kfree() while ipoctal_write_tty() and other tty ops
>> may still access it.
>>
>> Fix this by introducing kref-based lifetime management for the
>> ipoctal structure. A kref is taken in ipoctal_install() when a tty is
>> initialized, and released in ipoctal_cleanup() when the tty is finally
>> destroyed. The remove callback replaces direct kfree() with kref_put(),
>> ensuring the memory is only freed after all tty references have been
>> released.
>>
>> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
>> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
>> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
>> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
>> ---
>> drivers/ipack/devices/ipoctal.c | 19 +++++++++++++++++--
>> 1 file changed, 17 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
>> index 1bbefc6de708..079bda93ad5a 100644
>> --- a/drivers/ipack/devices/ipoctal.c
>> +++ b/drivers/ipack/devices/ipoctal.c
>> @@ -10,6 +10,7 @@
>> #include <linux/device.h>
>> #include <linux/module.h>
>> #include <linux/interrupt.h>
>> +#include <linux/kref.h>
>> #include <linux/sched.h>
>> #include <linux/tty.h>
>> #include <linux/serial.h>
>> @@ -25,6 +26,8 @@
>>
>> static const struct tty_operations ipoctal_fops;
>>
>> +static void ipoctal_release(struct kref *kref);
>> +
>> struct ipoctal_channel {
>> struct ipoctal_stats stats;
>> unsigned int nb_bytes;
>> @@ -49,6 +52,7 @@ struct ipoctal {
>> struct tty_driver *tty_drv;
>> u8 __iomem *mem8_space;
>> u8 __iomem *int_space;
>> + struct kref kref;
>> };
>>
>> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
>> @@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
>> if (res)
>> goto err_put_carrier;
>>
>> + kref_get(&ipoctal->kref);
>> tty->driver_data = channel;
>>
>> return 0;
>> @@ -666,6 +671,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>>
>> /* release the carrier driver */
>> ipack_put_carrier(ipoctal->dev);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static const struct tty_operations ipoctal_fops = {
>> @@ -683,6 +689,13 @@ static const struct tty_operations ipoctal_fops = {
>> .cleanup = ipoctal_cleanup,
>> };
>>
>> +static void ipoctal_release(struct kref *kref)
>> +{
>> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
>> +
>> + kfree(ipoctal);
>> +}
>> +
>> static int ipoctal_probe(struct ipack_device *dev)
>> {
>> int res;
>> @@ -692,6 +705,8 @@ static int ipoctal_probe(struct ipack_device *dev)
>> if (ipoctal == NULL)
>> return -ENOMEM;
>>
>> + kref_init(&ipoctal->kref);
>> +
>> ipoctal->dev = dev;
>> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
>> if (res)
>> @@ -701,7 +716,7 @@ static int ipoctal_probe(struct ipack_device *dev)
>> return 0;
>>
>> out_uninst:
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> return res;
>> }
>>
>> @@ -725,7 +740,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
>> tty_unregister_driver(ipoctal->tty_drv);
>> kfree(ipoctal->tty_drv->name);
>> tty_driver_kref_put(ipoctal->tty_drv);
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static void ipoctal_remove(struct ipack_device *idev)
>> --
>> 2.25.1
>>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref on remove
2026-06-24 19:47 ` Shuangpeng
2026-06-25 1:31 ` Pei Xiao
@ 2026-06-25 1:33 ` Pei Xiao
2026-06-25 2:10 ` Shuangpeng
1 sibling, 1 reply; 22+ messages in thread
From: Pei Xiao @ 2026-06-25 1:33 UTC (permalink / raw)
To: shuangpeng.kernel; +Cc: linux-kernel, Pei Xiao
A use-after-free occurs when the device is removed while a tty
session is still active. The remove callback frees the ipoctal
structure via kfree() while ipoctal_write_tty() and other tty ops
may still access it.
Fix the UAF by introducing kref-based lifetime management for the
ipoctal structure. A kref is taken in ipoctal_install() when a tty is
initialized, and released in ipoctal_cleanup() when the tty is finally
destroyed. The remove callback replaces direct kfree() with kref_put(),
ensuring the memory is only freed after all tty references have been
released.
However, keeping the ipoctal struct alive via kref exposed a null
pointer dereference: __ipoctal_remove() calls tty_port_free_xmit_buf()
which frees and NULLs xmit_buf, but a userspace process may still hold
the tty fd and call write(), leading to a crash in
ipoctal_copy_write_buffer() when it dereferences xmit_buf.
Fix this by checking for a NULL xmit_buf in ipoctal_write_tty() and
returning 0 if the buffer has been freed.
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
---
drivers/ipack/devices/ipoctal.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
index 1bbefc6de708..6bbbfb651dea 100644
--- a/drivers/ipack/devices/ipoctal.c
+++ b/drivers/ipack/devices/ipoctal.c
@@ -10,6 +10,7 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/serial.h>
@@ -25,6 +26,8 @@
static const struct tty_operations ipoctal_fops;
+static void ipoctal_release(struct kref *kref);
+
struct ipoctal_channel {
struct ipoctal_stats stats;
unsigned int nb_bytes;
@@ -49,6 +52,7 @@ struct ipoctal {
struct tty_driver *tty_drv;
u8 __iomem *mem8_space;
u8 __iomem *int_space;
+ struct kref kref;
};
static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
@@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
if (res)
goto err_put_carrier;
+ kref_get(&ipoctal->kref);
tty->driver_data = channel;
return 0;
@@ -462,6 +467,9 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
struct ipoctal_channel *channel = tty->driver_data;
size_t char_copied;
+ if (!channel->tty_port.xmit_buf)
+ return 0;
+
char_copied = ipoctal_copy_write_buffer(channel, buf, count);
/* As the IP-OCTAL 485 only supports half duplex, do it manually */
@@ -666,6 +674,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
/* release the carrier driver */
ipack_put_carrier(ipoctal->dev);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static const struct tty_operations ipoctal_fops = {
@@ -683,6 +692,13 @@ static const struct tty_operations ipoctal_fops = {
.cleanup = ipoctal_cleanup,
};
+static void ipoctal_release(struct kref *kref)
+{
+ struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
+
+ kfree(ipoctal);
+}
+
static int ipoctal_probe(struct ipack_device *dev)
{
int res;
@@ -692,6 +708,8 @@ static int ipoctal_probe(struct ipack_device *dev)
if (ipoctal == NULL)
return -ENOMEM;
+ kref_init(&ipoctal->kref);
+
ipoctal->dev = dev;
res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
if (res)
@@ -701,7 +719,7 @@ static int ipoctal_probe(struct ipack_device *dev)
return 0;
out_uninst:
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
return res;
}
@@ -725,7 +743,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
tty_unregister_driver(ipoctal->tty_drv);
kfree(ipoctal->tty_drv->name);
tty_driver_kref_put(ipoctal->tty_drv);
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static void ipoctal_remove(struct ipack_device *idev)
--
2.25.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref on remove
2026-06-25 1:33 ` [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
@ 2026-06-25 2:10 ` Shuangpeng
2026-06-25 3:24 ` [PATCH] " Pei Xiao
2026-06-25 3:25 ` [RFC] " Pei Xiao
0 siblings, 2 replies; 22+ messages in thread
From: Shuangpeng @ 2026-06-25 2:10 UTC (permalink / raw)
To: Pei Xiao; +Cc: linux-kernel
Hi Pei,
Thanks for the updated patch!
I applied it and reran the same reproducer. The previous crash in
ipoctal_write_tty() is no longer triggered, but the same reproducer now
hits another Oops after removing the device while the tty fd is still open.
The new report looks like this:
BUG: unable to handle page fault for address: ffffc9000617b005
#PF: supervisor write access in kernel mode
Oops: Oops: 0002 [#1] SMP KASAN PTI
RIP: 0010:iowrite8 (lib/iomap.c:207)
Call Trace:
ipoctal_set_termios (drivers/ipack/devices/ipoctal.c:66 drivers/ipack/devices/ipoctal.c:517)
tty_set_termios (drivers/tty/tty_ioctl.c:341)
set_termios (drivers/tty/tty_ioctl.c:516)
tty_mode_ioctl (drivers/tty/tty_ioctl.c:?)
tty_ioctl (drivers/tty/tty_io.c:2801)
__se_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583)
do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
The faulting access is from ipoctal_reset_channel():
drivers/ipack/devices/ipoctal.c:66
iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr);
called by ipoctal_set_termios():
drivers/ipack/devices/ipoctal.c:517
ipoctal_reset_channel(channel);
Best,
Shuangpeng
> On Jun 24, 2026, at 21:33, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>
> A use-after-free occurs when the device is removed while a tty
> session is still active. The remove callback frees the ipoctal
> structure via kfree() while ipoctal_write_tty() and other tty ops
> may still access it.
>
> Fix the UAF by introducing kref-based lifetime management for the
> ipoctal structure. A kref is taken in ipoctal_install() when a tty is
> initialized, and released in ipoctal_cleanup() when the tty is finally
> destroyed. The remove callback replaces direct kfree() with kref_put(),
> ensuring the memory is only freed after all tty references have been
> released.
>
> However, keeping the ipoctal struct alive via kref exposed a null
> pointer dereference: __ipoctal_remove() calls tty_port_free_xmit_buf()
> which frees and NULLs xmit_buf, but a userspace process may still hold
> the tty fd and call write(), leading to a crash in
> ipoctal_copy_write_buffer() when it dereferences xmit_buf.
>
> Fix this by checking for a NULL xmit_buf in ipoctal_write_tty() and
> returning 0 if the buffer has been freed.
>
> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
> ---
> drivers/ipack/devices/ipoctal.c | 22 ++++++++++++++++++++--
> 1 file changed, 20 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
> index 1bbefc6de708..6bbbfb651dea 100644
> --- a/drivers/ipack/devices/ipoctal.c
> +++ b/drivers/ipack/devices/ipoctal.c
> @@ -10,6 +10,7 @@
> #include <linux/device.h>
> #include <linux/module.h>
> #include <linux/interrupt.h>
> +#include <linux/kref.h>
> #include <linux/sched.h>
> #include <linux/tty.h>
> #include <linux/serial.h>
> @@ -25,6 +26,8 @@
>
> static const struct tty_operations ipoctal_fops;
>
> +static void ipoctal_release(struct kref *kref);
> +
> struct ipoctal_channel {
> struct ipoctal_stats stats;
> unsigned int nb_bytes;
> @@ -49,6 +52,7 @@ struct ipoctal {
> struct tty_driver *tty_drv;
> u8 __iomem *mem8_space;
> u8 __iomem *int_space;
> + struct kref kref;
> };
>
> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
> @@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
> if (res)
> goto err_put_carrier;
>
> + kref_get(&ipoctal->kref);
> tty->driver_data = channel;
>
> return 0;
> @@ -462,6 +467,9 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
> struct ipoctal_channel *channel = tty->driver_data;
> size_t char_copied;
>
> + if (!channel->tty_port.xmit_buf)
> + return 0;
> +
> char_copied = ipoctal_copy_write_buffer(channel, buf, count);
>
> /* As the IP-OCTAL 485 only supports half duplex, do it manually */
> @@ -666,6 +674,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>
> /* release the carrier driver */
> ipack_put_carrier(ipoctal->dev);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static const struct tty_operations ipoctal_fops = {
> @@ -683,6 +692,13 @@ static const struct tty_operations ipoctal_fops = {
> .cleanup = ipoctal_cleanup,
> };
>
> +static void ipoctal_release(struct kref *kref)
> +{
> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
> +
> + kfree(ipoctal);
> +}
> +
> static int ipoctal_probe(struct ipack_device *dev)
> {
> int res;
> @@ -692,6 +708,8 @@ static int ipoctal_probe(struct ipack_device *dev)
> if (ipoctal == NULL)
> return -ENOMEM;
>
> + kref_init(&ipoctal->kref);
> +
> ipoctal->dev = dev;
> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
> if (res)
> @@ -701,7 +719,7 @@ static int ipoctal_probe(struct ipack_device *dev)
> return 0;
>
> out_uninst:
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> return res;
> }
>
> @@ -725,7 +743,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
> tty_unregister_driver(ipoctal->tty_drv);
> kfree(ipoctal->tty_drv->name);
> tty_driver_kref_put(ipoctal->tty_drv);
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static void ipoctal_remove(struct ipack_device *idev)
> --
> 2.25.1
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH] ipack: ipoctal: fix use-after-free and null-ptr-deref on remove
2026-06-25 2:10 ` Shuangpeng
@ 2026-06-25 3:24 ` Pei Xiao
2026-06-26 1:40 ` Shuangpeng
2026-06-25 3:25 ` [RFC] " Pei Xiao
1 sibling, 1 reply; 22+ messages in thread
From: Pei Xiao @ 2026-06-25 3:24 UTC (permalink / raw)
To: shuangpeng.kernel; +Cc: linux-kernel, xiaopei01
A use-after-free occurs when the device is removed while a tty
session is still active. The remove callback frees the ipoctal
structure via kfree() while tty ops may still access it.
Fix this by introducing kref-based lifetime management. The kref is
initialized in probe(), taken in ipoctal_install() when a tty is
opened, and released in ipoctal_cleanup() when the tty is finally
destroyed. The remove callback replaces direct kfree() with
kref_put(), ensuring the memory is freed only after all references
are released.
However, the kref keeps only the ipoctal struct alive — the MMIO
mappings (devm_ioremap) and xmit_buf are released during remove()
regardless. If a tty fd is still open:
- tty_port_free_xmit_buf() NULLs xmit_buf, leading to a NULL
dereference in ipoctal_write_tty().
- devm iounmap() removes page table mappings, leading to a page
fault in ipoctal_set_termios() and other tty ops that access
channel->regs via iowrite8().
Fix both by:
(1) checking for NULL xmit_buf in ipoctal_write_tty(), and
(2) introducing a "removed" flag in struct ipoctal, set at the
start of __ipoctal_remove(), and checked in every tty op that
touches hardware registers (write_tty, set_termios,
port_activate, hangup, shutdown).
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
---
drivers/ipack/devices/ipoctal.c | 45 +++++++++++++++++++++++++++++++--
1 file changed, 43 insertions(+), 2 deletions(-)
diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
index 1bbefc6de708..2b9d381cf56d 100644
--- a/drivers/ipack/devices/ipoctal.c
+++ b/drivers/ipack/devices/ipoctal.c
@@ -10,6 +10,7 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/serial.h>
@@ -25,6 +26,8 @@
static const struct tty_operations ipoctal_fops;
+static void ipoctal_release(struct kref *kref);
+
struct ipoctal_channel {
struct ipoctal_stats stats;
unsigned int nb_bytes;
@@ -49,6 +52,8 @@ struct ipoctal {
struct tty_driver *tty_drv;
u8 __iomem *mem8_space;
u8 __iomem *int_space;
+ struct kref kref;
+ bool removed;
};
static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
@@ -70,8 +75,13 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel)
static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
{
struct ipoctal_channel *channel;
+ struct ipoctal *ipoctal;
channel = dev_get_drvdata(tty->dev);
+ ipoctal = chan_to_ipoctal(channel, tty->index);
+
+ if (ipoctal->removed)
+ return -ENODEV;
/*
* Enable RX. TX will be enabled when
@@ -95,6 +105,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
if (res)
goto err_put_carrier;
+ kref_get(&ipoctal->kref);
tty->driver_data = channel;
return 0;
@@ -460,8 +471,12 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
size_t count)
{
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
size_t char_copied;
+ if (ipoctal->removed || !channel->tty_port.xmit_buf)
+ return 0;
+
char_copied = ipoctal_copy_write_buffer(channel, buf, count);
/* As the IP-OCTAL 485 only supports half duplex, do it manually */
@@ -501,8 +516,12 @@ static void ipoctal_set_termios(struct tty_struct *tty,
unsigned char mr2 = 0;
unsigned char csr = 0;
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
speed_t baud;
+ if (ipoctal->removed)
+ return;
+
cflag = tty->termios.c_cflag;
/* Disable and reset everything before change the setup */
@@ -631,10 +650,15 @@ static void ipoctal_hangup(struct tty_struct *tty)
{
unsigned long flags;
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal;
if (channel == NULL)
return;
+ ipoctal = chan_to_ipoctal(channel, tty->index);
+ if (ipoctal->removed)
+ return;
+
spin_lock_irqsave(&channel->lock, flags);
channel->nb_bytes = 0;
channel->pointer_read = 0;
@@ -651,10 +675,15 @@ static void ipoctal_hangup(struct tty_struct *tty)
static void ipoctal_shutdown(struct tty_struct *tty)
{
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal;
if (channel == NULL)
return;
+ ipoctal = chan_to_ipoctal(channel, tty->index);
+ if (ipoctal->removed)
+ return;
+
ipoctal_reset_channel(channel);
tty_port_set_initialized(&channel->tty_port, false);
}
@@ -666,6 +695,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
/* release the carrier driver */
ipack_put_carrier(ipoctal->dev);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static const struct tty_operations ipoctal_fops = {
@@ -683,6 +713,13 @@ static const struct tty_operations ipoctal_fops = {
.cleanup = ipoctal_cleanup,
};
+static void ipoctal_release(struct kref *kref)
+{
+ struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
+
+ kfree(ipoctal);
+}
+
static int ipoctal_probe(struct ipack_device *dev)
{
int res;
@@ -692,6 +729,8 @@ static int ipoctal_probe(struct ipack_device *dev)
if (ipoctal == NULL)
return -ENOMEM;
+ kref_init(&ipoctal->kref);
+
ipoctal->dev = dev;
res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
if (res)
@@ -701,7 +740,7 @@ static int ipoctal_probe(struct ipack_device *dev)
return 0;
out_uninst:
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
return res;
}
@@ -709,6 +748,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
{
int i;
+ ipoctal->removed = true;
+
ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
for (i = 0; i < NR_CHANNELS; i++) {
@@ -725,7 +766,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
tty_unregister_driver(ipoctal->tty_drv);
kfree(ipoctal->tty_drv->name);
tty_driver_kref_put(ipoctal->tty_drv);
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static void ipoctal_remove(struct ipack_device *idev)
--
2.25.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref on remove
2026-06-25 2:10 ` Shuangpeng
2026-06-25 3:24 ` [PATCH] " Pei Xiao
@ 2026-06-25 3:25 ` Pei Xiao
1 sibling, 0 replies; 22+ messages in thread
From: Pei Xiao @ 2026-06-25 3:25 UTC (permalink / raw)
To: Shuangpeng; +Cc: linux-kernel
在 2026/6/25 10:10, Shuangpeng 写道:
> Hi Pei,
>
> Thanks for the updated patch!
>
> I applied it and reran the same reproducer. The previous crash in
> ipoctal_write_tty() is no longer triggered, but the same reproducer now
> hits another Oops after removing the device while the tty fd is still open.
>
> The new report looks like this:
>
> BUG: unable to handle page fault for address: ffffc9000617b005
> #PF: supervisor write access in kernel mode
> Oops: Oops: 0002 [#1] SMP KASAN PTI
> RIP: 0010:iowrite8 (lib/iomap.c:207)
>
> Call Trace:
> ipoctal_set_termios (drivers/ipack/devices/ipoctal.c:66 drivers/ipack/devices/ipoctal.c:517)
> tty_set_termios (drivers/tty/tty_ioctl.c:341)
> set_termios (drivers/tty/tty_ioctl.c:516)
> tty_mode_ioctl (drivers/tty/tty_ioctl.c:?)
> tty_ioctl (drivers/tty/tty_io.c:2801)
> __se_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583)
> do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
>
> The faulting access is from ipoctal_reset_channel():
>
> drivers/ipack/devices/ipoctal.c:66
> iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr);
>
> called by ipoctal_set_termios():
>
> drivers/ipack/devices/ipoctal.c:517
> ipoctal_reset_channel(channel);
thanks again!
I have already send v3 for test!
>
> Best,
> Shuangpeng
>
>
>> On Jun 24, 2026, at 21:33, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>>
>> A use-after-free occurs when the device is removed while a tty
>> session is still active. The remove callback frees the ipoctal
>> structure via kfree() while ipoctal_write_tty() and other tty ops
>> may still access it.
>>
>> Fix the UAF by introducing kref-based lifetime management for the
>> ipoctal structure. A kref is taken in ipoctal_install() when a tty is
>> initialized, and released in ipoctal_cleanup() when the tty is finally
>> destroyed. The remove callback replaces direct kfree() with kref_put(),
>> ensuring the memory is only freed after all tty references have been
>> released.
>>
>> However, keeping the ipoctal struct alive via kref exposed a null
>> pointer dereference: __ipoctal_remove() calls tty_port_free_xmit_buf()
>> which frees and NULLs xmit_buf, but a userspace process may still hold
>> the tty fd and call write(), leading to a crash in
>> ipoctal_copy_write_buffer() when it dereferences xmit_buf.
>>
>> Fix this by checking for a NULL xmit_buf in ipoctal_write_tty() and
>> returning 0 if the buffer has been freed.
>>
>> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
>> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
>> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
>> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
>> ---
>> drivers/ipack/devices/ipoctal.c | 22 ++++++++++++++++++++--
>> 1 file changed, 20 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
>> index 1bbefc6de708..6bbbfb651dea 100644
>> --- a/drivers/ipack/devices/ipoctal.c
>> +++ b/drivers/ipack/devices/ipoctal.c
>> @@ -10,6 +10,7 @@
>> #include <linux/device.h>
>> #include <linux/module.h>
>> #include <linux/interrupt.h>
>> +#include <linux/kref.h>
>> #include <linux/sched.h>
>> #include <linux/tty.h>
>> #include <linux/serial.h>
>> @@ -25,6 +26,8 @@
>>
>> static const struct tty_operations ipoctal_fops;
>>
>> +static void ipoctal_release(struct kref *kref);
>> +
>> struct ipoctal_channel {
>> struct ipoctal_stats stats;
>> unsigned int nb_bytes;
>> @@ -49,6 +52,7 @@ struct ipoctal {
>> struct tty_driver *tty_drv;
>> u8 __iomem *mem8_space;
>> u8 __iomem *int_space;
>> + struct kref kref;
>> };
>>
>> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
>> @@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
>> if (res)
>> goto err_put_carrier;
>>
>> + kref_get(&ipoctal->kref);
>> tty->driver_data = channel;
>>
>> return 0;
>> @@ -462,6 +467,9 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
>> struct ipoctal_channel *channel = tty->driver_data;
>> size_t char_copied;
>>
>> + if (!channel->tty_port.xmit_buf)
>> + return 0;
>> +
>> char_copied = ipoctal_copy_write_buffer(channel, buf, count);
>>
>> /* As the IP-OCTAL 485 only supports half duplex, do it manually */
>> @@ -666,6 +674,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>>
>> /* release the carrier driver */
>> ipack_put_carrier(ipoctal->dev);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static const struct tty_operations ipoctal_fops = {
>> @@ -683,6 +692,13 @@ static const struct tty_operations ipoctal_fops = {
>> .cleanup = ipoctal_cleanup,
>> };
>>
>> +static void ipoctal_release(struct kref *kref)
>> +{
>> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
>> +
>> + kfree(ipoctal);
>> +}
>> +
>> static int ipoctal_probe(struct ipack_device *dev)
>> {
>> int res;
>> @@ -692,6 +708,8 @@ static int ipoctal_probe(struct ipack_device *dev)
>> if (ipoctal == NULL)
>> return -ENOMEM;
>>
>> + kref_init(&ipoctal->kref);
>> +
>> ipoctal->dev = dev;
>> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
>> if (res)
>> @@ -701,7 +719,7 @@ static int ipoctal_probe(struct ipack_device *dev)
>> return 0;
>>
>> out_uninst:
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> return res;
>> }
>>
>> @@ -725,7 +743,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
>> tty_unregister_driver(ipoctal->tty_drv);
>> kfree(ipoctal->tty_drv->name);
>> tty_driver_kref_put(ipoctal->tty_drv);
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static void ipoctal_remove(struct ipack_device *idev)
>> --
>> 2.25.1
>>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH] ipack: ipoctal: fix use-after-free and null-ptr-deref on remove
2026-06-25 3:24 ` [PATCH] " Pei Xiao
@ 2026-06-26 1:40 ` Shuangpeng
2026-06-26 2:31 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup " Pei Xiao
2026-06-26 2:32 ` [PATCH] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
0 siblings, 2 replies; 22+ messages in thread
From: Shuangpeng @ 2026-06-26 1:40 UTC (permalink / raw)
To: Pei Xiao; +Cc: linux-kernel
Hi Pei,
I applied the new patch and reran the same reproducer [1]. It triggers
another KASAN report:
[ 63.599954][ T1151] BUG: KASAN: slab-use-after-free in ipoctal_cleanup (include/linux/ipack.h:292 drivers/ipack/devices/ipoctal.c:697)
[ 63.600513][ T1151] Read of size 8 at addr ffff8881136e5008 by task kworker/1:2/1151
[ 63.601248][ T1151] 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
[ 63.601249][ T1151] Workqueue: events release_one_tty
[ 63.601254][ T1151] Call Trace:
[ 63.601257][ T1151] <TASK>
[ 63.601258][ T1151] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[ 63.601261][ T1151] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[ 63.601272][ T1151] kasan_report (mm/kasan/report.c:595)
[ 63.601286][ T1151] ipoctal_cleanup (include/linux/ipack.h:292 drivers/ipack/devices/ipoctal.c:697)
[ 63.601289][ T1151] release_one_tty (drivers/tty/tty_io.c:1522)
[ 63.601292][ T1151] process_scheduled_works (kernel/workqueue.c:3322 kernel/workqueue.c:3405)
[ 63.601297][ T1151] worker_thread (kernel/workqueue.c:3486)
[ 63.601303][ T1151] kthread (kernel/kthread.c:436)
[ 63.601310][ T1151] ret_from_fork (kernel/process.c:158)
[ 63.601320][ T1151] ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
[ 63.601324][ T1151] </TASK>
[ 63.610982][ T1151] The buggy address belongs to the object at ffff8881136e5000
[ 63.610982][ T1151] which belongs to the cache kmalloc-1k of size 1024
[ 63.611938][ T1151] The buggy address is located 8 bytes inside of
[ 63.611938][ T1151] freed 1024-byte region [ffff8881136e5000, ffff8881136e5400)
I am also happy to test the next version if needed.
Best,
Shuangpeng
[1] https://gist.github.com/shuangpengbai/bc71ccd7ce1454abae45897fdde6813b
> On Jun 24, 2026, at 23:24, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>
> A use-after-free occurs when the device is removed while a tty
> session is still active. The remove callback frees the ipoctal
> structure via kfree() while tty ops may still access it.
>
> Fix this by introducing kref-based lifetime management. The kref is
> initialized in probe(), taken in ipoctal_install() when a tty is
> opened, and released in ipoctal_cleanup() when the tty is finally
> destroyed. The remove callback replaces direct kfree() with
> kref_put(), ensuring the memory is freed only after all references
> are released.
>
> However, the kref keeps only the ipoctal struct alive — the MMIO
> mappings (devm_ioremap) and xmit_buf are released during remove()
> regardless. If a tty fd is still open:
>
> - tty_port_free_xmit_buf() NULLs xmit_buf, leading to a NULL
> dereference in ipoctal_write_tty().
> - devm iounmap() removes page table mappings, leading to a page
> fault in ipoctal_set_termios() and other tty ops that access
> channel->regs via iowrite8().
>
> Fix both by:
>
> (1) checking for NULL xmit_buf in ipoctal_write_tty(), and
> (2) introducing a "removed" flag in struct ipoctal, set at the
> start of __ipoctal_remove(), and checked in every tty op that
> touches hardware registers (write_tty, set_termios,
> port_activate, hangup, shutdown).
>
> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
> ---
> drivers/ipack/devices/ipoctal.c | 45 +++++++++++++++++++++++++++++++--
> 1 file changed, 43 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
> index 1bbefc6de708..2b9d381cf56d 100644
> --- a/drivers/ipack/devices/ipoctal.c
> +++ b/drivers/ipack/devices/ipoctal.c
> @@ -10,6 +10,7 @@
> #include <linux/device.h>
> #include <linux/module.h>
> #include <linux/interrupt.h>
> +#include <linux/kref.h>
> #include <linux/sched.h>
> #include <linux/tty.h>
> #include <linux/serial.h>
> @@ -25,6 +26,8 @@
>
> static const struct tty_operations ipoctal_fops;
>
> +static void ipoctal_release(struct kref *kref);
> +
> struct ipoctal_channel {
> struct ipoctal_stats stats;
> unsigned int nb_bytes;
> @@ -49,6 +52,8 @@ struct ipoctal {
> struct tty_driver *tty_drv;
> u8 __iomem *mem8_space;
> u8 __iomem *int_space;
> + struct kref kref;
> + bool removed;
> };
>
> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
> @@ -70,8 +75,13 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel)
> static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
> {
> struct ipoctal_channel *channel;
> + struct ipoctal *ipoctal;
>
> channel = dev_get_drvdata(tty->dev);
> + ipoctal = chan_to_ipoctal(channel, tty->index);
> +
> + if (ipoctal->removed)
> + return -ENODEV;
>
> /*
> * Enable RX. TX will be enabled when
> @@ -95,6 +105,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
> if (res)
> goto err_put_carrier;
>
> + kref_get(&ipoctal->kref);
> tty->driver_data = channel;
>
> return 0;
> @@ -460,8 +471,12 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
> size_t count)
> {
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
> size_t char_copied;
>
> + if (ipoctal->removed || !channel->tty_port.xmit_buf)
> + return 0;
> +
> char_copied = ipoctal_copy_write_buffer(channel, buf, count);
>
> /* As the IP-OCTAL 485 only supports half duplex, do it manually */
> @@ -501,8 +516,12 @@ static void ipoctal_set_termios(struct tty_struct *tty,
> unsigned char mr2 = 0;
> unsigned char csr = 0;
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
> speed_t baud;
>
> + if (ipoctal->removed)
> + return;
> +
> cflag = tty->termios.c_cflag;
>
> /* Disable and reset everything before change the setup */
> @@ -631,10 +650,15 @@ static void ipoctal_hangup(struct tty_struct *tty)
> {
> unsigned long flags;
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal;
>
> if (channel == NULL)
> return;
>
> + ipoctal = chan_to_ipoctal(channel, tty->index);
> + if (ipoctal->removed)
> + return;
> +
> spin_lock_irqsave(&channel->lock, flags);
> channel->nb_bytes = 0;
> channel->pointer_read = 0;
> @@ -651,10 +675,15 @@ static void ipoctal_hangup(struct tty_struct *tty)
> static void ipoctal_shutdown(struct tty_struct *tty)
> {
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal;
>
> if (channel == NULL)
> return;
>
> + ipoctal = chan_to_ipoctal(channel, tty->index);
> + if (ipoctal->removed)
> + return;
> +
> ipoctal_reset_channel(channel);
> tty_port_set_initialized(&channel->tty_port, false);
> }
> @@ -666,6 +695,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>
> /* release the carrier driver */
> ipack_put_carrier(ipoctal->dev);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static const struct tty_operations ipoctal_fops = {
> @@ -683,6 +713,13 @@ static const struct tty_operations ipoctal_fops = {
> .cleanup = ipoctal_cleanup,
> };
>
> +static void ipoctal_release(struct kref *kref)
> +{
> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
> +
> + kfree(ipoctal);
> +}
> +
> static int ipoctal_probe(struct ipack_device *dev)
> {
> int res;
> @@ -692,6 +729,8 @@ static int ipoctal_probe(struct ipack_device *dev)
> if (ipoctal == NULL)
> return -ENOMEM;
>
> + kref_init(&ipoctal->kref);
> +
> ipoctal->dev = dev;
> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
> if (res)
> @@ -701,7 +740,7 @@ static int ipoctal_probe(struct ipack_device *dev)
> return 0;
>
> out_uninst:
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> return res;
> }
>
> @@ -709,6 +748,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
> {
> int i;
>
> + ipoctal->removed = true;
> +
> ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
>
> for (i = 0; i < NR_CHANNELS; i++) {
> @@ -725,7 +766,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
> tty_unregister_driver(ipoctal->tty_drv);
> kfree(ipoctal->tty_drv->name);
> tty_driver_kref_put(ipoctal->tty_drv);
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static void ipoctal_remove(struct ipack_device *idev)
> --
> 2.25.1
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove
2026-06-26 1:40 ` Shuangpeng
@ 2026-06-26 2:31 ` Pei Xiao
2026-06-26 2:31 ` [PATCH 2/2] ipack: ipoctal: add rwsem to guard against TOCTOU in remove path Pei Xiao
2026-06-30 7:40 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove Pei Xiao
2026-06-26 2:32 ` [PATCH] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
1 sibling, 2 replies; 22+ messages in thread
From: Pei Xiao @ 2026-06-26 2:31 UTC (permalink / raw)
To: shuangpeng.kernel; +Cc: linux-kernel, xiaopei01
Three issues arise when the device is removed while a tty session is
still active:
1. UAF of struct ipoctal: the remove callback frees ipoctal via
kfree() while tty ops may still access it. Fix by introducing
kref-based lifetime management — kref is taken in install() when
a tty is opened and released in cleanup() when the tty is finally
destroyed; remove() uses kref_put() instead of kfree().
2. NULL dereference in ipoctal_write_tty(): __ipoctal_remove()
frees xmit_buf via tty_port_free_xmit_buf() while a userspace
process may still hold the tty fd and call write(). Fix by
checking for NULL xmit_buf in ipoctal_write_tty().
3. UAF in ipoctal_cleanup(): ipack_put_carrier(ipoctal->dev)
dereferences ipoctal->dev after the ipack_device has been freed
by ipack_device_del(). Fix by caching ipoctal->carrier_owner
during probe() and calling module_put() on the cached pointer
directly in cleanup(), avoiding any access to ipoctal->dev.
Also introduce a "removed" flag in struct ipoctal, set at the start
of __ipoctal_remove(), and checked in every tty op that accesses
hardware resources (port_activate, write_tty, set_termios, hangup,
shutdown). This prevents page faults when devm_ioremap() regions
are unmapped after remove() returns.
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
---
drivers/ipack/devices/ipoctal.c | 56 ++++++++++++++++++++++++++++++---
1 file changed, 52 insertions(+), 4 deletions(-)
diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
index 1bbefc6de708..bf71b8952a7c 100644
--- a/drivers/ipack/devices/ipoctal.c
+++ b/drivers/ipack/devices/ipoctal.c
@@ -10,6 +10,7 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/serial.h>
@@ -25,6 +26,8 @@
static const struct tty_operations ipoctal_fops;
+static void ipoctal_release(struct kref *kref);
+
struct ipoctal_channel {
struct ipoctal_stats stats;
unsigned int nb_bytes;
@@ -49,6 +52,9 @@ struct ipoctal {
struct tty_driver *tty_drv;
u8 __iomem *mem8_space;
u8 __iomem *int_space;
+ struct kref kref;
+ struct module *carrier_owner;
+ bool removed;
};
static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
@@ -70,8 +76,14 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel)
static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
{
struct ipoctal_channel *channel;
+ struct ipoctal *ipoctal;
channel = dev_get_drvdata(tty->dev);
+ ipoctal = chan_to_ipoctal(channel, tty->index);
+
+
+ if (ipoctal->removed)
+ return -ENODEV;
/*
* Enable RX. TX will be enabled when
@@ -95,6 +107,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
if (res)
goto err_put_carrier;
+ kref_get(&ipoctal->kref);
tty->driver_data = channel;
return 0;
@@ -460,8 +473,13 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
size_t count)
{
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
size_t char_copied;
+
+ if (ipoctal->removed || !channel->tty_port.xmit_buf)
+ return 0;
+
char_copied = ipoctal_copy_write_buffer(channel, buf, count);
/* As the IP-OCTAL 485 only supports half duplex, do it manually */
@@ -501,8 +519,13 @@ static void ipoctal_set_termios(struct tty_struct *tty,
unsigned char mr2 = 0;
unsigned char csr = 0;
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
speed_t baud;
+
+ if (ipoctal->removed)
+ return;
+
cflag = tty->termios.c_cflag;
/* Disable and reset everything before change the setup */
@@ -631,10 +654,16 @@ static void ipoctal_hangup(struct tty_struct *tty)
{
unsigned long flags;
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal;
if (channel == NULL)
return;
+ ipoctal = chan_to_ipoctal(channel, tty->index);
+
+ if (ipoctal->removed)
+ return;
+
spin_lock_irqsave(&channel->lock, flags);
channel->nb_bytes = 0;
channel->pointer_read = 0;
@@ -651,10 +680,16 @@ static void ipoctal_hangup(struct tty_struct *tty)
static void ipoctal_shutdown(struct tty_struct *tty)
{
struct ipoctal_channel *channel = tty->driver_data;
+ struct ipoctal *ipoctal;
if (channel == NULL)
return;
+ ipoctal = chan_to_ipoctal(channel, tty->index);
+
+ if (ipoctal->removed)
+ return;
+
ipoctal_reset_channel(channel);
tty_port_set_initialized(&channel->tty_port, false);
}
@@ -664,8 +699,9 @@ static void ipoctal_cleanup(struct tty_struct *tty)
struct ipoctal_channel *channel = tty->driver_data;
struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
- /* release the carrier driver */
- ipack_put_carrier(ipoctal->dev);
+ /* release the carrier driver via cached owner */
+ module_put(ipoctal->carrier_owner);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static const struct tty_operations ipoctal_fops = {
@@ -683,6 +719,13 @@ static const struct tty_operations ipoctal_fops = {
.cleanup = ipoctal_cleanup,
};
+static void ipoctal_release(struct kref *kref)
+{
+ struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
+
+ kfree(ipoctal);
+}
+
static int ipoctal_probe(struct ipack_device *dev)
{
int res;
@@ -692,7 +735,10 @@ static int ipoctal_probe(struct ipack_device *dev)
if (ipoctal == NULL)
return -ENOMEM;
+ kref_init(&ipoctal->kref);
+
ipoctal->dev = dev;
+ ipoctal->carrier_owner = dev->bus->owner;
res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
if (res)
goto out_uninst;
@@ -701,7 +747,7 @@ static int ipoctal_probe(struct ipack_device *dev)
return 0;
out_uninst:
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
return res;
}
@@ -709,6 +755,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
{
int i;
+ ipoctal->removed = true;
+
ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
for (i = 0; i < NR_CHANNELS; i++) {
@@ -725,7 +773,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
tty_unregister_driver(ipoctal->tty_drv);
kfree(ipoctal->tty_drv->name);
tty_driver_kref_put(ipoctal->tty_drv);
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
}
static void ipoctal_remove(struct ipack_device *idev)
--
2.25.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 2/2] ipack: ipoctal: add rwsem to guard against TOCTOU in remove path
2026-06-26 2:31 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup " Pei Xiao
@ 2026-06-26 2:31 ` Pei Xiao
2026-06-30 7:40 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove Pei Xiao
1 sibling, 0 replies; 22+ messages in thread
From: Pei Xiao @ 2026-06-26 2:31 UTC (permalink / raw)
To: shuangpeng.kernel; +Cc: linux-kernel, xiaopei01
The "removed" flag check in each tty op has a TOCTOU race with
__ipoctal_remove(): the device could be removed between the flag
check and the subsequent access to hardware resources (channel
registers via iowrite8, or xmit_buf in write_tty).
Close this race by introducing a read-write semaphore (remove_sem).
The tty ops acquire the read lock via guard(rwsem_read) for the
full duration of the operation, while __ipoctal_remove() acquires
the write lock via scoped_guard(rwsem_write) when setting the
removed flag. This ensures that once removed is true, no in-flight
tty op can still be accessing resources that are about to be freed
by the remove path.
Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
---
drivers/ipack/devices/ipoctal.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
index bf71b8952a7c..2169e4b75f98 100644
--- a/drivers/ipack/devices/ipoctal.c
+++ b/drivers/ipack/devices/ipoctal.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/kref.h>
+#include <linux/rwsem.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/serial.h>
@@ -54,6 +55,7 @@ struct ipoctal {
u8 __iomem *int_space;
struct kref kref;
struct module *carrier_owner;
+ struct rw_semaphore remove_sem;
bool removed;
};
@@ -81,7 +83,7 @@ static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
channel = dev_get_drvdata(tty->dev);
ipoctal = chan_to_ipoctal(channel, tty->index);
-
+ guard(rwsem_read)(&ipoctal->remove_sem);
if (ipoctal->removed)
return -ENODEV;
@@ -476,7 +478,7 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
size_t char_copied;
-
+ guard(rwsem_read)(&ipoctal->remove_sem);
if (ipoctal->removed || !channel->tty_port.xmit_buf)
return 0;
@@ -522,7 +524,7 @@ static void ipoctal_set_termios(struct tty_struct *tty,
struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
speed_t baud;
-
+ guard(rwsem_read)(&ipoctal->remove_sem);
if (ipoctal->removed)
return;
@@ -660,7 +662,7 @@ static void ipoctal_hangup(struct tty_struct *tty)
return;
ipoctal = chan_to_ipoctal(channel, tty->index);
-
+ guard(rwsem_read)(&ipoctal->remove_sem);
if (ipoctal->removed)
return;
@@ -686,7 +688,7 @@ static void ipoctal_shutdown(struct tty_struct *tty)
return;
ipoctal = chan_to_ipoctal(channel, tty->index);
-
+ guard(rwsem_read)(&ipoctal->remove_sem);
if (ipoctal->removed)
return;
@@ -736,6 +738,7 @@ static int ipoctal_probe(struct ipack_device *dev)
return -ENOMEM;
kref_init(&ipoctal->kref);
+ init_rwsem(&ipoctal->remove_sem);
ipoctal->dev = dev;
ipoctal->carrier_owner = dev->bus->owner;
@@ -755,7 +758,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
{
int i;
- ipoctal->removed = true;
+ scoped_guard(rwsem_write, &ipoctal->remove_sem)
+ ipoctal->removed = true;
ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
--
2.25.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH] ipack: ipoctal: fix use-after-free and null-ptr-deref on remove
2026-06-26 1:40 ` Shuangpeng
2026-06-26 2:31 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup " Pei Xiao
@ 2026-06-26 2:32 ` Pei Xiao
1 sibling, 0 replies; 22+ messages in thread
From: Pei Xiao @ 2026-06-26 2:32 UTC (permalink / raw)
To: Shuangpeng; +Cc: linux-kernel
在 2026/6/26 09:40, Shuangpeng 写道:
> Hi Pei,
>
> I applied the new patch and reran the same reproducer [1]. It triggers
> another KASAN report:
>
> [ 63.599954][ T1151] BUG: KASAN: slab-use-after-free in ipoctal_cleanup (include/linux/ipack.h:292 drivers/ipack/devices/ipoctal.c:697)
> [ 63.600513][ T1151] Read of size 8 at addr ffff8881136e5008 by task kworker/1:2/1151
> [ 63.601248][ T1151] 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
> [ 63.601249][ T1151] Workqueue: events release_one_tty
> [ 63.601254][ T1151] Call Trace:
> [ 63.601257][ T1151] <TASK>
> [ 63.601258][ T1151] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
> [ 63.601261][ T1151] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
> [ 63.601272][ T1151] kasan_report (mm/kasan/report.c:595)
> [ 63.601286][ T1151] ipoctal_cleanup (include/linux/ipack.h:292 drivers/ipack/devices/ipoctal.c:697)
> [ 63.601289][ T1151] release_one_tty (drivers/tty/tty_io.c:1522)
> [ 63.601292][ T1151] process_scheduled_works (kernel/workqueue.c:3322 kernel/workqueue.c:3405)
> [ 63.601297][ T1151] worker_thread (kernel/workqueue.c:3486)
> [ 63.601303][ T1151] kthread (kernel/kthread.c:436)
> [ 63.601310][ T1151] ret_from_fork (kernel/process.c:158)
> [ 63.601320][ T1151] ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> [ 63.601324][ T1151] </TASK>
> [ 63.610982][ T1151] The buggy address belongs to the object at ffff8881136e5000
> [ 63.610982][ T1151] which belongs to the cache kmalloc-1k of size 1024
> [ 63.611938][ T1151] The buggy address is located 8 bytes inside of
> [ 63.611938][ T1151] freed 1024-byte region [ffff8881136e5000, ffff8881136e5400)
>
> I am also happy to test the next version if needed.
You're really a warm-hearted member of the Linux kernel community.
thanks!~
> Best,
> Shuangpeng
>
> [1] https://gist.github.com/shuangpengbai/bc71ccd7ce1454abae45897fdde6813b
>
>
>> On Jun 24, 2026, at 23:24, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>>
>> A use-after-free occurs when the device is removed while a tty
>> session is still active. The remove callback frees the ipoctal
>> structure via kfree() while tty ops may still access it.
>>
>> Fix this by introducing kref-based lifetime management. The kref is
>> initialized in probe(), taken in ipoctal_install() when a tty is
>> opened, and released in ipoctal_cleanup() when the tty is finally
>> destroyed. The remove callback replaces direct kfree() with
>> kref_put(), ensuring the memory is freed only after all references
>> are released.
>>
>> However, the kref keeps only the ipoctal struct alive — the MMIO
>> mappings (devm_ioremap) and xmit_buf are released during remove()
>> regardless. If a tty fd is still open:
>>
>> - tty_port_free_xmit_buf() NULLs xmit_buf, leading to a NULL
>> dereference in ipoctal_write_tty().
>> - devm iounmap() removes page table mappings, leading to a page
>> fault in ipoctal_set_termios() and other tty ops that access
>> channel->regs via iowrite8().
>>
>> Fix both by:
>>
>> (1) checking for NULL xmit_buf in ipoctal_write_tty(), and
>> (2) introducing a "removed" flag in struct ipoctal, set at the
>> start of __ipoctal_remove(), and checked in every tty op that
>> touches hardware registers (write_tty, set_termios,
>> port_activate, hangup, shutdown).
>>
>> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
>> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
>> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
>> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
>> ---
>> drivers/ipack/devices/ipoctal.c | 45 +++++++++++++++++++++++++++++++--
>> 1 file changed, 43 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
>> index 1bbefc6de708..2b9d381cf56d 100644
>> --- a/drivers/ipack/devices/ipoctal.c
>> +++ b/drivers/ipack/devices/ipoctal.c
>> @@ -10,6 +10,7 @@
>> #include <linux/device.h>
>> #include <linux/module.h>
>> #include <linux/interrupt.h>
>> +#include <linux/kref.h>
>> #include <linux/sched.h>
>> #include <linux/tty.h>
>> #include <linux/serial.h>
>> @@ -25,6 +26,8 @@
>>
>> static const struct tty_operations ipoctal_fops;
>>
>> +static void ipoctal_release(struct kref *kref);
>> +
>> struct ipoctal_channel {
>> struct ipoctal_stats stats;
>> unsigned int nb_bytes;
>> @@ -49,6 +52,8 @@ struct ipoctal {
>> struct tty_driver *tty_drv;
>> u8 __iomem *mem8_space;
>> u8 __iomem *int_space;
>> + struct kref kref;
>> + bool removed;
>> };
>>
>> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
>> @@ -70,8 +75,13 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel)
>> static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
>> {
>> struct ipoctal_channel *channel;
>> + struct ipoctal *ipoctal;
>>
>> channel = dev_get_drvdata(tty->dev);
>> + ipoctal = chan_to_ipoctal(channel, tty->index);
>> +
>> + if (ipoctal->removed)
>> + return -ENODEV;
>>
>> /*
>> * Enable RX. TX will be enabled when
>> @@ -95,6 +105,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
>> if (res)
>> goto err_put_carrier;
>>
>> + kref_get(&ipoctal->kref);
>> tty->driver_data = channel;
>>
>> return 0;
>> @@ -460,8 +471,12 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
>> size_t count)
>> {
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
>> size_t char_copied;
>>
>> + if (ipoctal->removed || !channel->tty_port.xmit_buf)
>> + return 0;
>> +
>> char_copied = ipoctal_copy_write_buffer(channel, buf, count);
>>
>> /* As the IP-OCTAL 485 only supports half duplex, do it manually */
>> @@ -501,8 +516,12 @@ static void ipoctal_set_termios(struct tty_struct *tty,
>> unsigned char mr2 = 0;
>> unsigned char csr = 0;
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
>> speed_t baud;
>>
>> + if (ipoctal->removed)
>> + return;
>> +
>> cflag = tty->termios.c_cflag;
>>
>> /* Disable and reset everything before change the setup */
>> @@ -631,10 +650,15 @@ static void ipoctal_hangup(struct tty_struct *tty)
>> {
>> unsigned long flags;
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal;
>>
>> if (channel == NULL)
>> return;
>>
>> + ipoctal = chan_to_ipoctal(channel, tty->index);
>> + if (ipoctal->removed)
>> + return;
>> +
>> spin_lock_irqsave(&channel->lock, flags);
>> channel->nb_bytes = 0;
>> channel->pointer_read = 0;
>> @@ -651,10 +675,15 @@ static void ipoctal_hangup(struct tty_struct *tty)
>> static void ipoctal_shutdown(struct tty_struct *tty)
>> {
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal;
>>
>> if (channel == NULL)
>> return;
>>
>> + ipoctal = chan_to_ipoctal(channel, tty->index);
>> + if (ipoctal->removed)
>> + return;
>> +
>> ipoctal_reset_channel(channel);
>> tty_port_set_initialized(&channel->tty_port, false);
>> }
>> @@ -666,6 +695,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>>
>> /* release the carrier driver */
>> ipack_put_carrier(ipoctal->dev);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static const struct tty_operations ipoctal_fops = {
>> @@ -683,6 +713,13 @@ static const struct tty_operations ipoctal_fops = {
>> .cleanup = ipoctal_cleanup,
>> };
>>
>> +static void ipoctal_release(struct kref *kref)
>> +{
>> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
>> +
>> + kfree(ipoctal);
>> +}
>> +
>> static int ipoctal_probe(struct ipack_device *dev)
>> {
>> int res;
>> @@ -692,6 +729,8 @@ static int ipoctal_probe(struct ipack_device *dev)
>> if (ipoctal == NULL)
>> return -ENOMEM;
>>
>> + kref_init(&ipoctal->kref);
>> +
>> ipoctal->dev = dev;
>> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
>> if (res)
>> @@ -701,7 +740,7 @@ static int ipoctal_probe(struct ipack_device *dev)
>> return 0;
>>
>> out_uninst:
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> return res;
>> }
>>
>> @@ -709,6 +748,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
>> {
>> int i;
>>
>> + ipoctal->removed = true;
>> +
>> ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
>>
>> for (i = 0; i < NR_CHANNELS; i++) {
>> @@ -725,7 +766,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
>> tty_unregister_driver(ipoctal->tty_drv);
>> kfree(ipoctal->tty_drv->name);
>> tty_driver_kref_put(ipoctal->tty_drv);
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static void ipoctal_remove(struct ipack_device *idev)
>> --
>> 2.25.1
>>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove
2026-06-26 2:31 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup " Pei Xiao
2026-06-26 2:31 ` [PATCH 2/2] ipack: ipoctal: add rwsem to guard against TOCTOU in remove path Pei Xiao
@ 2026-06-30 7:40 ` Pei Xiao
2026-06-30 19:10 ` Shuangpeng
1 sibling, 1 reply; 22+ messages in thread
From: Pei Xiao @ 2026-06-30 7:40 UTC (permalink / raw)
To: shuangpeng.kernel; +Cc: linux-kernel
hi shuangpeng,
If this version have no bugs. I will send it to Greg for review.
Thanks!
在 2026/6/26 10:31, Pei Xiao 写道:
> Three issues arise when the device is removed while a tty session is
> still active:
>
> 1. UAF of struct ipoctal: the remove callback frees ipoctal via
> kfree() while tty ops may still access it. Fix by introducing
> kref-based lifetime management — kref is taken in install() when
> a tty is opened and released in cleanup() when the tty is finally
> destroyed; remove() uses kref_put() instead of kfree().
>
> 2. NULL dereference in ipoctal_write_tty(): __ipoctal_remove()
> frees xmit_buf via tty_port_free_xmit_buf() while a userspace
> process may still hold the tty fd and call write(). Fix by
> checking for NULL xmit_buf in ipoctal_write_tty().
>
> 3. UAF in ipoctal_cleanup(): ipack_put_carrier(ipoctal->dev)
> dereferences ipoctal->dev after the ipack_device has been freed
> by ipack_device_del(). Fix by caching ipoctal->carrier_owner
> during probe() and calling module_put() on the cached pointer
> directly in cleanup(), avoiding any access to ipoctal->dev.
>
> Also introduce a "removed" flag in struct ipoctal, set at the start
> of __ipoctal_remove(), and checked in every tty op that accesses
> hardware resources (port_activate, write_tty, set_termios, hangup,
> shutdown). This prevents page faults when devm_ioremap() regions
> are unmapped after remove() returns.
>
> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
> ---
> drivers/ipack/devices/ipoctal.c | 56 ++++++++++++++++++++++++++++++---
> 1 file changed, 52 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
> index 1bbefc6de708..bf71b8952a7c 100644
> --- a/drivers/ipack/devices/ipoctal.c
> +++ b/drivers/ipack/devices/ipoctal.c
> @@ -10,6 +10,7 @@
> #include <linux/device.h>
> #include <linux/module.h>
> #include <linux/interrupt.h>
> +#include <linux/kref.h>
> #include <linux/sched.h>
> #include <linux/tty.h>
> #include <linux/serial.h>
> @@ -25,6 +26,8 @@
>
> static const struct tty_operations ipoctal_fops;
>
> +static void ipoctal_release(struct kref *kref);
> +
> struct ipoctal_channel {
> struct ipoctal_stats stats;
> unsigned int nb_bytes;
> @@ -49,6 +52,9 @@ struct ipoctal {
> struct tty_driver *tty_drv;
> u8 __iomem *mem8_space;
> u8 __iomem *int_space;
> + struct kref kref;
> + struct module *carrier_owner;
> + bool removed;
> };
>
> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
> @@ -70,8 +76,14 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel)
> static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
> {
> struct ipoctal_channel *channel;
> + struct ipoctal *ipoctal;
>
> channel = dev_get_drvdata(tty->dev);
> + ipoctal = chan_to_ipoctal(channel, tty->index);
> +
> +
> + if (ipoctal->removed)
> + return -ENODEV;
>
> /*
> * Enable RX. TX will be enabled when
> @@ -95,6 +107,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
> if (res)
> goto err_put_carrier;
>
> + kref_get(&ipoctal->kref);
> tty->driver_data = channel;
>
> return 0;
> @@ -460,8 +473,13 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
> size_t count)
> {
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
> size_t char_copied;
>
> +
> + if (ipoctal->removed || !channel->tty_port.xmit_buf)
> + return 0;
> +
> char_copied = ipoctal_copy_write_buffer(channel, buf, count);
>
> /* As the IP-OCTAL 485 only supports half duplex, do it manually */
> @@ -501,8 +519,13 @@ static void ipoctal_set_termios(struct tty_struct *tty,
> unsigned char mr2 = 0;
> unsigned char csr = 0;
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
> speed_t baud;
>
> +
> + if (ipoctal->removed)
> + return;
> +
> cflag = tty->termios.c_cflag;
>
> /* Disable and reset everything before change the setup */
> @@ -631,10 +654,16 @@ static void ipoctal_hangup(struct tty_struct *tty)
> {
> unsigned long flags;
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal;
>
> if (channel == NULL)
> return;
>
> + ipoctal = chan_to_ipoctal(channel, tty->index);
> +
> + if (ipoctal->removed)
> + return;
> +
> spin_lock_irqsave(&channel->lock, flags);
> channel->nb_bytes = 0;
> channel->pointer_read = 0;
> @@ -651,10 +680,16 @@ static void ipoctal_hangup(struct tty_struct *tty)
> static void ipoctal_shutdown(struct tty_struct *tty)
> {
> struct ipoctal_channel *channel = tty->driver_data;
> + struct ipoctal *ipoctal;
>
> if (channel == NULL)
> return;
>
> + ipoctal = chan_to_ipoctal(channel, tty->index);
> +
> + if (ipoctal->removed)
> + return;
> +
> ipoctal_reset_channel(channel);
> tty_port_set_initialized(&channel->tty_port, false);
> }
> @@ -664,8 +699,9 @@ static void ipoctal_cleanup(struct tty_struct *tty)
> struct ipoctal_channel *channel = tty->driver_data;
> struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
>
> - /* release the carrier driver */
> - ipack_put_carrier(ipoctal->dev);
> + /* release the carrier driver via cached owner */
> + module_put(ipoctal->carrier_owner);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static const struct tty_operations ipoctal_fops = {
> @@ -683,6 +719,13 @@ static const struct tty_operations ipoctal_fops = {
> .cleanup = ipoctal_cleanup,
> };
>
> +static void ipoctal_release(struct kref *kref)
> +{
> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
> +
> + kfree(ipoctal);
> +}
> +
> static int ipoctal_probe(struct ipack_device *dev)
> {
> int res;
> @@ -692,7 +735,10 @@ static int ipoctal_probe(struct ipack_device *dev)
> if (ipoctal == NULL)
> return -ENOMEM;
>
> + kref_init(&ipoctal->kref);
> +
> ipoctal->dev = dev;
> + ipoctal->carrier_owner = dev->bus->owner;
> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
> if (res)
> goto out_uninst;
> @@ -701,7 +747,7 @@ static int ipoctal_probe(struct ipack_device *dev)
> return 0;
>
> out_uninst:
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> return res;
> }
>
> @@ -709,6 +755,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
> {
> int i;
>
> + ipoctal->removed = true;
> +
> ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
>
> for (i = 0; i < NR_CHANNELS; i++) {
> @@ -725,7 +773,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
> tty_unregister_driver(ipoctal->tty_drv);
> kfree(ipoctal->tty_drv->name);
> tty_driver_kref_put(ipoctal->tty_drv);
> - kfree(ipoctal);
> + kref_put(&ipoctal->kref, ipoctal_release);
> }
>
> static void ipoctal_remove(struct ipack_device *idev)
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove
2026-06-30 7:40 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove Pei Xiao
@ 2026-06-30 19:10 ` Shuangpeng
0 siblings, 0 replies; 22+ messages in thread
From: Shuangpeng @ 2026-06-30 19:10 UTC (permalink / raw)
To: Pei Xiao; +Cc: linux-kernel
Hi Pei,
I tried this patch on my side and no bug is triggered.
Thanks for your fix!
Best,
Shuangpeng
> On Jun 30, 2026, at 03:40, Pei Xiao <xiaopei01@kylinos.cn> wrote:
>
> hi shuangpeng,
>
> If this version have no bugs. I will send it to Greg for review.
> Thanks!
>
> 在 2026/6/26 10:31, Pei Xiao 写道:
>> Three issues arise when the device is removed while a tty session is
>> still active:
>>
>> 1. UAF of struct ipoctal: the remove callback frees ipoctal via
>> kfree() while tty ops may still access it. Fix by introducing
>> kref-based lifetime management — kref is taken in install() when
>> a tty is opened and released in cleanup() when the tty is finally
>> destroyed; remove() uses kref_put() instead of kfree().
>>
>> 2. NULL dereference in ipoctal_write_tty(): __ipoctal_remove()
>> frees xmit_buf via tty_port_free_xmit_buf() while a userspace
>> process may still hold the tty fd and call write(). Fix by
>> checking for NULL xmit_buf in ipoctal_write_tty().
>>
>> 3. UAF in ipoctal_cleanup(): ipack_put_carrier(ipoctal->dev)
>> dereferences ipoctal->dev after the ipack_device has been freed
>> by ipack_device_del(). Fix by caching ipoctal->carrier_owner
>> during probe() and calling module_put() on the cached pointer
>> directly in cleanup(), avoiding any access to ipoctal->dev.
>>
>> Also introduce a "removed" flag in struct ipoctal, set at the start
>> of __ipoctal_remove(), and checked in every tty op that accesses
>> hardware resources (port_activate, write_tty, set_termios, hangup,
>> shutdown). This prevents page faults when devm_ioremap() regions
>> are unmapped after remove() returns.
>>
>> Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
>> Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@gmail.com/
>> Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
>> Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
>> ---
>> drivers/ipack/devices/ipoctal.c | 56 ++++++++++++++++++++++++++++++---
>> 1 file changed, 52 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
>> index 1bbefc6de708..bf71b8952a7c 100644
>> --- a/drivers/ipack/devices/ipoctal.c
>> +++ b/drivers/ipack/devices/ipoctal.c
>> @@ -10,6 +10,7 @@
>> #include <linux/device.h>
>> #include <linux/module.h>
>> #include <linux/interrupt.h>
>> +#include <linux/kref.h>
>> #include <linux/sched.h>
>> #include <linux/tty.h>
>> #include <linux/serial.h>
>> @@ -25,6 +26,8 @@
>>
>> static const struct tty_operations ipoctal_fops;
>>
>> +static void ipoctal_release(struct kref *kref);
>> +
>> struct ipoctal_channel {
>> struct ipoctal_stats stats;
>> unsigned int nb_bytes;
>> @@ -49,6 +52,9 @@ struct ipoctal {
>> struct tty_driver *tty_drv;
>> u8 __iomem *mem8_space;
>> u8 __iomem *int_space;
>> + struct kref kref;
>> + struct module *carrier_owner;
>> + bool removed;
>> };
>>
>> static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
>> @@ -70,8 +76,14 @@ static void ipoctal_reset_channel(struct ipoctal_channel *channel)
>> static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty)
>> {
>> struct ipoctal_channel *channel;
>> + struct ipoctal *ipoctal;
>>
>> channel = dev_get_drvdata(tty->dev);
>> + ipoctal = chan_to_ipoctal(channel, tty->index);
>> +
>> +
>> + if (ipoctal->removed)
>> + return -ENODEV;
>>
>> /*
>> * Enable RX. TX will be enabled when
>> @@ -95,6 +107,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
>> if (res)
>> goto err_put_carrier;
>>
>> + kref_get(&ipoctal->kref);
>> tty->driver_data = channel;
>>
>> return 0;
>> @@ -460,8 +473,13 @@ static ssize_t ipoctal_write_tty(struct tty_struct *tty, const u8 *buf,
>> size_t count)
>> {
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
>> size_t char_copied;
>>
>> +
>> + if (ipoctal->removed || !channel->tty_port.xmit_buf)
>> + return 0;
>> +
>> char_copied = ipoctal_copy_write_buffer(channel, buf, count);
>>
>> /* As the IP-OCTAL 485 only supports half duplex, do it manually */
>> @@ -501,8 +519,13 @@ static void ipoctal_set_termios(struct tty_struct *tty,
>> unsigned char mr2 = 0;
>> unsigned char csr = 0;
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
>> speed_t baud;
>>
>> +
>> + if (ipoctal->removed)
>> + return;
>> +
>> cflag = tty->termios.c_cflag;
>>
>> /* Disable and reset everything before change the setup */
>> @@ -631,10 +654,16 @@ static void ipoctal_hangup(struct tty_struct *tty)
>> {
>> unsigned long flags;
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal;
>>
>> if (channel == NULL)
>> return;
>>
>> + ipoctal = chan_to_ipoctal(channel, tty->index);
>> +
>> + if (ipoctal->removed)
>> + return;
>> +
>> spin_lock_irqsave(&channel->lock, flags);
>> channel->nb_bytes = 0;
>> channel->pointer_read = 0;
>> @@ -651,10 +680,16 @@ static void ipoctal_hangup(struct tty_struct *tty)
>> static void ipoctal_shutdown(struct tty_struct *tty)
>> {
>> struct ipoctal_channel *channel = tty->driver_data;
>> + struct ipoctal *ipoctal;
>>
>> if (channel == NULL)
>> return;
>>
>> + ipoctal = chan_to_ipoctal(channel, tty->index);
>> +
>> + if (ipoctal->removed)
>> + return;
>> +
>> ipoctal_reset_channel(channel);
>> tty_port_set_initialized(&channel->tty_port, false);
>> }
>> @@ -664,8 +699,9 @@ static void ipoctal_cleanup(struct tty_struct *tty)
>> struct ipoctal_channel *channel = tty->driver_data;
>> struct ipoctal *ipoctal = chan_to_ipoctal(channel, tty->index);
>>
>> - /* release the carrier driver */
>> - ipack_put_carrier(ipoctal->dev);
>> + /* release the carrier driver via cached owner */
>> + module_put(ipoctal->carrier_owner);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static const struct tty_operations ipoctal_fops = {
>> @@ -683,6 +719,13 @@ static const struct tty_operations ipoctal_fops = {
>> .cleanup = ipoctal_cleanup,
>> };
>>
>> +static void ipoctal_release(struct kref *kref)
>> +{
>> + struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
>> +
>> + kfree(ipoctal);
>> +}
>> +
>> static int ipoctal_probe(struct ipack_device *dev)
>> {
>> int res;
>> @@ -692,7 +735,10 @@ static int ipoctal_probe(struct ipack_device *dev)
>> if (ipoctal == NULL)
>> return -ENOMEM;
>>
>> + kref_init(&ipoctal->kref);
>> +
>> ipoctal->dev = dev;
>> + ipoctal->carrier_owner = dev->bus->owner;
>> res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
>> if (res)
>> goto out_uninst;
>> @@ -701,7 +747,7 @@ static int ipoctal_probe(struct ipack_device *dev)
>> return 0;
>>
>> out_uninst:
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> return res;
>> }
>>
>> @@ -709,6 +755,8 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
>> {
>> int i;
>>
>> + ipoctal->removed = true;
>> +
>> ipoctal->dev->bus->ops->free_irq(ipoctal->dev);
>>
>> for (i = 0; i < NR_CHANNELS; i++) {
>> @@ -725,7 +773,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
>> tty_unregister_driver(ipoctal->tty_drv);
>> kfree(ipoctal->tty_drv->name);
>> tty_driver_kref_put(ipoctal->tty_drv);
>> - kfree(ipoctal);
>> + kref_put(&ipoctal->kref, ipoctal_release);
>> }
>>
>> static void ipoctal_remove(struct ipack_device *idev)
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [BUG] KASAN: slab-use-after-free in ipoctal_write_tty
2026-06-16 2:46 ` Greg KH
2026-06-16 16:26 ` Shuangpeng
@ 2026-07-01 2:11 ` Pei Xiao
1 sibling, 0 replies; 22+ messages in thread
From: Pei Xiao @ 2026-07-01 2:11 UTC (permalink / raw)
To: Greg KH, Shuangpeng
Cc: vaibhavgupta40, jens.taprogge, kees, industrypack-devel, linux-kernel
在 2026/6/16 10:46, Greg KH 写道:
> On Mon, Jun 15, 2026 at 08:11:49PM -0400, Shuangpeng wrote:
>>
>>> On Jun 15, 2026, at 16:49, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>
>>> On Mon, Jun 15, 2026 at 04:33:09PM -0400, Shuangpeng wrote:
>>>>
>>>>> On Jun 15, 2026, at 00:03, Greg KH <gregkh@linuxfoundation.org> wrote:
>>>>>
>>>>> On Sun, Jun 14, 2026 at 03:48:50PM -0400, Shuangpeng Bai wrote:
>>>>>> Hi Kernel Maintainers,
>>>>>>
>>>>>> I hit the following report while testing current upstream kernel:
>>>>>>
>>>>>> KASAN: slab-use-after-free in ipoctal_write_tty
>>>>> Cool, do you have this hardware, or is this only virtual testing?
>>>> No, I do not have the physical hardware. This was reproduced with
>>>> unmodified QEMU using its existing TPCI200/IP-Octal emulation.
>>>>
>>>>> If virtual, are you sure that the hardware is being emulated properly?
>>>>
>>>> I understand this is not the same as testing on real hardware. However,
>>>> my current understanding is that the crash is triggered after a
>>>> successful probe through the normal sysfs unbind/remove path while the
>>>> ipoctal tty fd is still open. The failing path does not seem to rely on
>>>> device-specific emulation details after probe, but rather on the
>>>> lifetime of the tty/device state during removal.
>>> What specific sysfs unbind path? That's only for root and for testing
>>> kernel development, it's not a normal thing that a user does at all,
>>> right?
>>>
>> The sysfs path used by the reproducer is:
>>
>> /sys/bus/pci/drivers/tpci200/unbind
>>
>> So yes, this is a root-only kernel testing/development path, not a
>> normal unprivileged user path.
>>
>>>> Please let me know if I am missing anything here. I would also
>>>> appreciate any suggestions on what I could check to better evaluate
>>>> whether the emulation is appropriate for this report.
>>> What exactly are you trying to test?
>> I was trying to test whether the driver handles open ipoctal tty file
>> descriptors safely when the backing TPCI200/IPack device is removed.
> As you found, it doesn't :)
>
> See the discussions about device unbind and misc/char device nodes on
> the mailing lists for many messages about this and potential ways to
> resolve it. As it's not a real issue for drivers like this, it's a very
> low priority for other people to resolve, but we will always gladly
> review patches from others.
Hi Greg,
I have send two pathes for this bug. Could you please take a look?
link:
https://lore.kernel.org/lkml/cover.1782870760.git.xiaopei01@kylinos.cn/
Pei.
thanks!
> thanks,
>
> greg k-h
>
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-07-01 2:11 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-14 19:48 [BUG] KASAN: slab-use-after-free in ipoctal_write_tty Shuangpeng Bai
2026-06-15 4:03 ` Greg KH
2026-06-15 20:33 ` Shuangpeng
2026-06-15 20:49 ` Greg KH
2026-06-16 0:11 ` Shuangpeng
2026-06-16 2:46 ` Greg KH
2026-06-16 16:26 ` Shuangpeng
2026-06-23 7:46 ` Pei Xiao
2026-06-23 7:50 ` [RFC] ipack: ipoctal: fix use-after-free on remove Pei Xiao
2026-06-24 19:47 ` Shuangpeng
2026-06-25 1:31 ` Pei Xiao
2026-06-25 1:33 ` [RFC] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
2026-06-25 2:10 ` Shuangpeng
2026-06-25 3:24 ` [PATCH] " Pei Xiao
2026-06-26 1:40 ` Shuangpeng
2026-06-26 2:31 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup " Pei Xiao
2026-06-26 2:31 ` [PATCH 2/2] ipack: ipoctal: add rwsem to guard against TOCTOU in remove path Pei Xiao
2026-06-30 7:40 ` [PATCH 1/2] ipack: ipoctal: fix UAF, null-ptr-deref, and use-after-free in cleanup on remove Pei Xiao
2026-06-30 19:10 ` Shuangpeng
2026-06-26 2:32 ` [PATCH] ipack: ipoctal: fix use-after-free and null-ptr-deref " Pei Xiao
2026-06-25 3:25 ` [RFC] " Pei Xiao
2026-07-01 2:11 ` [BUG] KASAN: slab-use-after-free in ipoctal_write_tty Pei Xiao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Powered by JetHome