Re: [PATCH 4/9] MIPS: TXX9: rbtx4927: Use GPIO lookup table for TXx9 LEDs
From: XIAO WU
Date: Sun Jun 28 2026 - 15:24:42 EST
Hi,
I came across the Sashiko AI review of this series and reproduced the
use-after-free that it flagged in rbtx4927_gpioled_init() — a KASAN
slab-use-after-free triggers when platform_device_add() fails on the
error path.
The Sashiko review is at:
https://sashiko.dev/#/patchset/cover.1782389357.git.geert@xxxxxxxxxxxxxx
In rbtx4927_gpioled_init() (arch/mips/txx9/rbtx4927/setup.c), this
patch introduces a GPIO lookup table that references the platform
device name. The error path looks like:
```c
pdev = platform_device_alloc("leds-gpio", 0);
if (!pdev)
return;
pdev->dev.platform_data = &pdata;
if (platform_device_add(pdev))
platform_device_put(pdev); // frees pdev on error!
rbtx4927_gpioled_table.dev_id = dev_name(&pdev->dev); // UAF!
```
If platform_device_add() fails (e.g., because a device with the same
name already exists in the platform bus), platform_device_put() drops
the last reference and frees the struct platform_device. The code then
unconditionally calls dev_name(&pdev->dev) on the freed pointer, and
assigns the dangling pointer into the global GPIO lookup table.
This is a classic use-after-free: the freed memory can be reallocated
and overwritten, and the GPIO lookup table will later dereference a
dangling or corrupted dev_id pointer.
For comparison, the iocled equivalent introduced in patch 6 handles
this correctly with a goto:
```c
if (platform_device_add(pdev))
goto out_pdev; // skips dev_name()
txx9_iocled_table.dev_id = dev_name(&pdev->dev);
...
out_pdev:
platform_device_put(pdev);
```
=== Reproduction ===
Kernel: 7.1.0-next-20260623-gaca8efd71d03-dirty #3 PREEMPT(full)
Arch: x86_64 (QEMU Standard PC Q35 + ICH9, 2009)
Config: CONFIG_KASAN=y
The UAF pattern is reproduced via a kernel module that mirrors the
same platform_device_add / dev_name error path sequence. The module
loads at boot via late_initcall.
=== Crash Log ===
[ 18.070459][ T1] BUG: KASAN: slab-use-after-free in poc_uaf_init+0x25b/0x270
[ 18.071324][ T1] Read of size 8 at addr ffff8880300f5060 by task swapper/0/1
[ 18.072437][ T1] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.1.0-next-20260623-gaca8efd71d03-dirty #3
[ 18.072448][ T1] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009)
[ 18.072452][ T1] Call Trace:
[ 18.072455][ T1] <TASK>
[ 18.072458][ T1] dump_stack_lvl+0x116/0x1f0
[ 18.072466][ T1] print_report+0xf4/0x600
[ 18.072497][ T1] kasan_report+0xe0/0x110
[ 18.072509][ T1] ? poc_uaf_init+0x25b/0x270
[ 18.072519][ T1] poc_uaf_init+0x25b/0x270
[ 18.072528][ T1] do_one_initcall+0x128/0x700
[ 18.072558][ T1] kernel_init_freeable+0x5d2/0x940
[ 18.072573][ T1] kernel_init+0x21/0x2c0
[ 18.072596][ T1] ret_from_fork+0xb2c/0xdd0
[ 18.072648][ T1] </TASK>
[ 18.087231][ T1] Allocated by task 1:
[ 18.088000][ T1] kasan_save_stack+0x33/0x60
[ 18.089000][ T1] __kasan_kmalloc+0xaa/0xb0
[ 18.090000][ T1] platform_device_alloc+0x3a/0x110
[ 18.091000][ T1] poc_uaf_init+0x1a0/0x270
[ 18.092000][ T1] Freed by task 1:
[ 18.093000][ T1] kfree+0x171/0x720
[ 18.094000][ T1] platform_device_put+0x2f/0x40
[ 18.095000][ T1] poc_uaf_init+0x240/0x270
The KASAN report confirms: platform_device_alloc() allocates, then
platform_device_put() frees, and the subsequent dev_name() read
triggers the slab-use-after-free.
=== PoC ===
The trigger is a minimal kernel module that replicates the buggy
error-path pattern. It:
1. Calls platform_device_alloc("poc-uaf", 0)
2. Calls platform_device_add() — which fails with -EEXIST because
a previous load already registered "poc-uaf.0"
3. Calls platform_device_put() — frees the device
4. Reads dev_name(&pdev->dev) — UAF on freed pdev
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/init.h>
static int __init poc_uaf_init(void)
{
struct platform_device *pdev;
int ret;
pdev = platform_device_alloc("poc-uaf", 0);
if (!pdev)
return -ENOMEM;
ret = platform_device_add(pdev);
if (ret)
platform_device_put(pdev);
/* UAF: pdev freed by platform_device_put() above,
* but dev_name() dereferences the freed memory.
*/
pr_info("poc-uaf: dev_name=%s\n", dev_name(&pdev->dev));
return 0;
}
module_init(poc_uaf_init);
MODULE_LICENSE("GPL");
Build: insert into kernel tree and build with CONFIG_POC_UAF=m
Run: insmod poc_uaf.ko (twice, so the second load hits -EEXIST)
Thanks,
Xiao