* [PATCH 0/1] rust: soc: avoid dangling attributes when SoC bus is unavailable
@ 2026-10-27 17:48 Priya Bala Govindasamy
2026-10-27 17:48 ` [PATCH 1/1] " Priya Bala Govindasamy
0 siblings, 1 reply; 2+ messages in thread
From: Priya Bala Govindasamy @ 2026-10-27 17:48 UTC (permalink / raw)
To: gregkh, rafael, dakr, ojeda
Cc: boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross,
daniel.almeida, tamird, acourbot, work, mmaurer, driver-core,
rust-for-linux, ardalan, zhiyunq, dzueck, pgovind2
Dear Linux kernel maintainers,
We are developing a tool called FerroLens to detect potential
unsoundness in Rust code in the Linux kernel. The tool
internally uses an LLM to detect bugs.
We then perform manual analysis to verify these reports.
FerroLens reported the following bug in rust/kernel/soc.rs:
`Registration::new()` calls the C function `soc_device_register()`
to register a SoC device with the SoC core. It passes a pointer to the
`soc_device_attribute` object of its pinned `BuiltAttributes` object.
In `soc_device_register()`, if the SoC bus is not registered, this pointer
argument is saved in a global variable `early_soc_dev_attr` for deferred
registration and NULL is returned.
If `Registration::new()` receives a NULL pointer, it produces `EINVAL`,
causing the `BuiltAttributes` object to be dropped. This frees the memory
containing the attribute data, which is still referenced by
`early_soc_dev_attr`. A later call to `soc_device_match()` will dereference
this dangling pointer, resulting in a use-after-free bug.
The SoC bus is normally registered by a core initcall before loadable
modules run, but the condition is reachable if that initcall fails or
is skipped.
Below is a PoC kernel module that demonstrates such a UAF.
"initcall_blacklist=soc_bus_register" is added as a kernel command line
argument to prevent the SoC bus from being registered and trigger the
aforementioned error path. The module attempts
to register a SoC device, which fails with EINVAL. The module then calls
soc_device_match() to check if the SoC device is present, which
results in a UAF.
// SPDX-License-Identifier: GPL-2.0
use kernel::{
prelude::*,
soc::{Attributes, Registration},
str::CString,
bindings
};
module! {
type: SocPoc,
name: "soc_poc",
authors: ["Priya Govindasamy"],
description: "PoC demonstrating potential UAF due to Registration::new()",
license: "GPL",
}
fn matches_soc() -> bool {
let matches = [
bindings::soc_device_attribute {
machine: core::ptr::null(),
family: c"Snapdragon".as_char_ptr(),
revision: core::ptr::null(),
serial_number: core::ptr::null(),
soc_id: c"SM8650".as_char_ptr(),
data: core::ptr::null(),
custom_attr_group: core::ptr::null(),
},
// Required zero-terminated sentinel.
bindings::soc_device_attribute::default(),
];
// SAFETY: `matches` is a valid, zero-terminated array, and all string
// pointers remain valid throughout the call.
let result = unsafe { bindings::soc_device_match(matches.as_ptr()) };
!result.is_null()
}
struct SocPoc {
_registration: Pin<KBox<Registration>>,
}
impl kernel::Module for SocPoc {
fn init(_module: &'static ThisModule) -> Result<Self> {
let attributes = Attributes {
machine: Some(CString::try_from(c"Rust SoC PoC board")?),
family: Some(CString::try_from(c"Rust SoC PoC")?),
revision: Some(CString::try_from(c"1.0")?),
serial_number: Some(CString::try_from(c"0001")?),
soc_id: Some(CString::try_from(c"RUST-SOC-POC")?),
};
let registration = match KBox::pin_init(Registration::new(attributes), GFP_KERNEL) {
Ok(registration) => {
pr_info!("soc_poc: SoC registration succeeded\n");
registration
}
Err(err) => {
pr_err!("soc_poc: SoC registration failed: {err:?}\n");
let _matches_soc = matches_soc();
return Err(err);
}
};
Ok(Self {
_registration: registration,
})
}
}
Output:
[ 226.710823] soc_poc: soc_poc: SoC registration failed: EINVAL
[ 226.710876] ==================================================================
[ 226.710881] BUG: KASAN: slab-use-after-free in soc_device_match_attr+0xb1/0x190
[ 226.710929] Read of size 8 at addr ffff88810c770288 by task insmod/4521
[ 226.710966] CPU: 4 UID: 0 PID: 4521 Comm: insmod Tainted: G OE N 7.3.0-rc5+ #76 PREEMPT(lazy)
[ 226.710974] Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE, [N]=TEST
[ 226.710976] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 226.710983] Call Trace:
[ 226.710987] <TASK>
[ 226.710996] dump_stack_lvl+0x97/0xe0
[ 226.711027] print_report+0x175/0x6f0
[ 226.711049] ? __virt_addr_valid+0x18d/0x3b0
[ 226.711070] ? __virt_addr_valid+0x344/0x3b0
[ 226.711074] ? soc_device_match_attr+0xb1/0x190
[ 226.711076] ? kasan_complete_mode_report_info+0x75/0x230
[ 226.711082] ? soc_device_match_attr+0xb1/0x190
[ 226.711085] kasan_report+0xfa/0x130
[ 226.711088] ? soc_device_match_attr+0xb1/0x190
[ 226.711095] __asan_report_load8_noabort+0x18/0x20
[ 226.711099] soc_device_match_attr+0xb1/0x190
[ 226.711102] soc_device_match+0xd1/0x100
[ 226.711106] _RNvXCs6F4l9FIlPI9_7soc_pocNtB2_6SocPocNtNtCsbuTvttuFvbr_6kernel6module6Module4init+0x61b/0x7c0 [soc_poc]
[ 226.711124] init_module+0x15/0xff0 [soc_poc]
[ 226.711128] do_one_initcall+0x119/0x4f0
[ 226.711132] ? do_init_module+0x63/0x740
[ 226.711159] ? __pfx_init_module+0x10/0x10 [soc_poc]
[ 226.711163] ? do_syscall_64+0x11c/0x3f0
[ 226.711183] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 226.711193] ? kvm_sched_clock_read+0x15/0x40
[ 226.711203] ? __lock_acquire+0xeeb/0x1040
[ 226.711219] ? kvm_sched_clock_read+0x15/0x40
[ 226.711223] ? debug_smp_processor_id+0x1b/0x30
[ 226.711227] ? _raw_spin_unlock_irqrestore+0x38/0x90
[ 226.711239] ? __this_cpu_preempt_check+0x17/0x20
[ 226.711242] ? lockdep_hardirqs_on+0xdc/0x160
[ 226.711247] ? debug_smp_processor_id+0x1b/0x30
[ 226.711254] ? kasan_save_alloc_info+0x3a/0x50
[ 226.711257] ? __kasan_kmalloc+0x94/0xb0
[ 226.711261] ? __kmalloc_cache_noprof+0x35b/0x6c0
[ 226.711267] ? do_init_module+0x63/0x740
[ 226.711270] ? __kmalloc_cache_noprof+0x165/0x6c0
[ 226.711272] ? kasan_unpoison+0x4c/0x70
[ 226.711276] ? kasan_poison+0x44/0x60
[ 226.711281] do_init_module+0x23f/0x740
[ 226.711288] load_module+0x32dc/0x3f20
[ 226.711297] __se_sys_finit_module+0x39d/0x5f0
[ 226.711308] __x64_sys_finit_module+0x7f/0x90
[ 226.711312] x64_sys_call+0xa66/0x2600
[ 226.711316] do_syscall_64+0x11c/0x3f0
[ 226.711319] ? vm_mmap_pgoff+0x25d/0x360
[ 226.711336] ? up_write+0x1b4/0x4c0
[ 226.711338] ? __this_cpu_preempt_check+0x17/0x20
[ 226.711343] ? vm_mmap_pgoff+0x269/0x360
[ 226.711348] ? fput+0x60/0x90
[ 226.711358] ? ksys_mmap_pgoff+0x426/0x630
[ 226.711374] ? debug_smp_processor_id+0x1b/0x30
[ 226.711379] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 226.711384] ? __this_cpu_preempt_check+0x17/0x20
[ 226.711387] ? lockdep_hardirqs_on+0xdc/0x160
[ 226.711391] ? debug_smp_processor_id+0x1b/0x30
[ 226.711394] ? rcu_is_watching+0x1a/0xc0
[ 226.711401] ? trace_irq_disable+0x3c/0x130
[ 226.711419] ? do_syscall_64+0x6d/0x3f0
[ 226.711422] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 226.711426] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 226.711429] RIP: 0033:0x7f391451e9dd
[ 226.711440] Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 23 b4 0f 00 f7 d8 64 89 01 48
[ 226.711446] RSP: 002b:00007ffd4da673b8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
[ 226.711457] RAX: ffffffffffffffda RBX: 000055a3792b57c0 RCX: 00007f391451e9dd
[ 226.711460] RDX: 0000000000000000 RSI: 000055a3514efcd2 RDI: 0000000000000003
[ 226.711462] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
[ 226.711464] R10: 0000000000000003 R11: 0000000000000246 R12: 000055a3514efcd2
[ 226.711466] R13: 000055a3792b5790 R14: 000055a3514ee888 R15: 000055a3792b58d0
[ 226.711473] </TASK>
[ 226.711475] Allocated by task 4521:
[ 226.711481] kasan_save_track+0x2f/0x70
[ 226.711490] kasan_save_alloc_info+0x3a/0x50
[ 226.711492] __kasan_kmalloc+0x94/0xb0
[ 226.711494] __kmalloc_node_track_caller_noprof+0x56c/0x850
[ 226.711497] krealloc_node_align_noprof+0x189/0x3b0
[ 226.711500] rust_helper_krealloc_node_align+0xd/0x20
[ 226.711517] _RNvXCs6F4l9FIlPI9_7soc_pocNtB2_6SocPocNtNtCsbuTvttuFvbr_6kernel6module6Module4init+0x1eb/0x7c0 [soc_poc]
[ 226.711524] init_module+0x15/0xff0 [soc_poc]
[ 226.711527] do_one_initcall+0x119/0x4f0
[ 226.711530] do_init_module+0x23f/0x740
[ 226.711533] load_module+0x32dc/0x3f20
[ 226.711535] __se_sys_finit_module+0x39d/0x5f0
[ 226.711537] __x64_sys_finit_module+0x7f/0x90
[ 226.711539] x64_sys_call+0xa66/0x2600
[ 226.711541] do_syscall_64+0x11c/0x3f0
[ 226.711544] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 226.711547] Freed by task 4521:
[ 226.711549] kasan_save_track+0x2f/0x70
[ 226.711552] kasan_save_free_info+0x44/0x50
[ 226.711555] __kasan_slab_free+0x63/0x90
[ 226.711557] kfree+0x1c3/0x680
[ 226.711565] krealloc_node_align_noprof+0x238/0x3b0
[ 226.711567] rust_helper_krealloc_node_align+0xd/0x20
[ 226.711570] _RNvXCs6F4l9FIlPI9_7soc_pocNtB2_6SocPocNtNtCsbuTvttuFvbr_6kernel6module6Module4init+0x525/0x7c0 [soc_poc]
[ 226.711573] init_module+0x15/0xff0 [soc_poc]
[ 226.711576] do_one_initcall+0x119/0x4f0
[ 226.711579] do_init_module+0x23f/0x740
[ 226.711581] load_module+0x32dc/0x3f20
[ 226.711583] __se_sys_finit_module+0x39d/0x5f0
[ 226.711585] __x64_sys_finit_module+0x7f/0x90
[ 226.711587] x64_sys_call+0xa66/0x2600
[ 226.711589] do_syscall_64+0x11c/0x3f0
[ 226.711592] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 226.711595] The buggy address belongs to the object at ffff88810c770200
which belongs to the cache kmalloc-192 of size 192
[ 226.711601] The buggy address is located 136 bytes inside of
freed 192-byte region [ffff88810c770200, ffff88810c7702c0)
[ 226.711605] The buggy address belongs to the physical page:
[ 226.711613] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10c770
[ 226.711627] head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 226.711630] flags: 0x17ffffc0000040(head|node=0|zone=2|lastcpupid=0x1fffff)
[ 226.711637] page_type: f5(slab)
[ 226.711656] raw: 0017ffffc0000040 ffff8881000423c0 dead000000000100 dead000000000122
[ 226.711659] raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000
[ 226.711661] head: 0017ffffc0000040 ffff8881000423c0 dead000000000100 dead000000000122
[ 226.711663] head: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000
[ 226.711665] head: 0017ffffc0000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff
[ 226.711667] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000002
[ 226.711668] page dumped because: kasan: bad access detected
[ 226.711676] Memory state around the buggy address:
[ 226.711681] ffff88810c770180: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[ 226.711682] ffff88810c770200: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 226.711687] >ffff88810c770280: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[ 226.711688] ^
[ 226.711690] ffff88810c770300: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 226.711692] ffff88810c770380: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[ 226.711693] ==================================================================
[ 226.711696] Disabling lock debugging due to kernel taint
Priya Bala Govindasamy (1):
rust: soc: avoid dangling attributes when SoC bus is unavailable
drivers/base/soc.c | 19 ++++++++++++++++++-
include/linux/sys_soc.h | 18 ++++++++++++++++++
rust/kernel/soc.rs | 7 ++++---
3 files changed, 40 insertions(+), 4 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH 1/1] rust: soc: avoid dangling attributes when SoC bus is unavailable
2026-10-27 17:48 [PATCH 0/1] rust: soc: avoid dangling attributes when SoC bus is unavailable Priya Bala Govindasamy
@ 2026-10-27 17:48 ` Priya Bala Govindasamy
0 siblings, 0 replies; 2+ messages in thread
From: Priya Bala Govindasamy @ 2026-10-27 17:48 UTC (permalink / raw)
To: gregkh, rafael, dakr, ojeda
Cc: boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross,
daniel.almeida, tamird, acourbot, work, mmaurer, driver-core,
rust-for-linux, ardalan, zhiyunq, dzueck, pgovind2
`Registration::new()` calls the C function `soc_device_register()`
to register a SoC device with the SoC core. It passes a pointer to the
`soc_device_attribute` object of its pinned `BuiltAttributes` object.
In `soc_device_register()`, if the SoC bus is not registered, this pointer
argument is saved in a global variable `early_soc_dev_attr` for deferred
registration and NULL is returned.
If `Registration::new()` receives a NULL pointer, it produces `EINVAL`,
causing the `BuiltAttributes` object to be dropped. This frees the memory
containing the attribute data, which is still referenced by
`early_soc_dev_attr`. A later call to `soc_device_match()` will dereference
this dangling pointer, resulting in a use-after-free bug.
The SoC bus is normally registered by a core initcall before loadable
modules run, but the condition is reachable if that initcall fails or
is skipped, as demonstrated using initcall_blacklist=soc_bus_register
kernel command line argument.
Fix this by adding a new function `soc_device_register_nodefer` which
never returns NULL. When the SoC bus is not registered,
it returns `ERR_PTR(-EPROBE_DEFER)` and does not retain a pointer
to the attribute data.
Make the Rust interface in `Registration::new()` call this new function.
The interface remains unchanged for other users of the existing
`soc_device_register` function.
Fixes: 057d44b05775 ("rust: Add soc_device support")
Reported-by: Dylan Zueck <dzueck@uci.edu>
Assisted-by: LLM
Signed-off-by: Priya Bala Govindasamy <pgovind2@uci.edu>
---
drivers/base/soc.c | 19 ++++++++++++++++++-
include/linux/sys_soc.h | 18 ++++++++++++++++++
rust/kernel/soc.rs | 7 ++++---
3 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/drivers/base/soc.c b/drivers/base/soc.c
index af7d71393774..dc9072281427 100644
--- a/drivers/base/soc.c
+++ b/drivers/base/soc.c
@@ -122,12 +122,17 @@ EXPORT_SYMBOL_GPL(soc_attr_read_machine);
static struct soc_device_attribute *early_soc_dev_attr;
-struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr)
+static struct soc_device *
+__soc_device_register(struct soc_device_attribute *soc_dev_attr,
+ bool allow_defer)
{
struct soc_device *soc_dev;
const struct attribute_group **soc_attr_groups;
int ret;
+ if (!soc_bus_registered && !allow_defer)
+ return ERR_PTR(-EPROBE_DEFER);
+
soc_attr_read_machine(soc_dev_attr);
if (!soc_bus_registered) {
@@ -179,8 +184,20 @@ struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr
out1:
return ERR_PTR(ret);
}
+
+struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr)
+{
+ return __soc_device_register(soc_dev_attr, true);
+}
EXPORT_SYMBOL_GPL(soc_device_register);
+struct soc_device *
+soc_device_register_nodefer(struct soc_device_attribute *soc_dev_attr)
+{
+ return __soc_device_register(soc_dev_attr, false);
+}
+EXPORT_SYMBOL_GPL(soc_device_register_nodefer);
+
/* Ensure soc_dev->attr is freed after calling soc_device_unregister. */
void soc_device_unregister(struct soc_device *soc_dev)
{
diff --git a/include/linux/sys_soc.h b/include/linux/sys_soc.h
index cedabf177f47..4bd9ee43be74 100644
--- a/include/linux/sys_soc.h
+++ b/include/linux/sys_soc.h
@@ -26,10 +26,28 @@ struct soc_device_attribute {
* - %NULL if the SoC bus is not yet registered;
* - on success, the newly allocated &struct soc_device pointer;
* - on failure, a negative error code as an ERR_PTR().
+ *
+ * If this function returns %NULL, it retains @soc_plat_dev_attr for deferred
+ * registration. The caller must keep it and all data it points to valid.
*/
struct soc_device *soc_device_register(
struct soc_device_attribute *soc_plat_dev_attr);
+/**
+ * soc_device_register_nodefer - register SoC as a device without deferring
+ * @soc_plat_dev_attr: Attributes passed from platform to be attributed to a SoC
+ *
+ * Unlike soc_device_register(), this function never returns %NULL. It retains
+ * @soc_plat_dev_attr only when it returns a valid &struct soc_device pointer.
+ *
+ * Returns:
+ * - on success, the newly allocated &struct soc_device pointer;
+ * - if the SoC bus is unavailable, ERR_PTR(-EPROBE_DEFER);
+ * - on other failures, a negative error code as an ERR_PTR().
+ */
+struct soc_device *soc_device_register_nodefer(
+ struct soc_device_attribute *soc_plat_dev_attr);
+
/**
* soc_device_unregister - unregister SoC device
* @soc_dev: SoC device to be unregistered
diff --git a/rust/kernel/soc.rs b/rust/kernel/soc.rs
index 0d6a36c83cb6..39659dbd31f8 100644
--- a/rust/kernel/soc.rs
+++ b/rust/kernel/soc.rs
@@ -122,10 +122,11 @@ pub fn new(attr: Attributes) -> impl PinInit<Self, Error> {
// * The struct provided through attr is backed by pinned data next to it,
// so as long as attr lives, the strings pointed to by the struct will too.
// * `attr` is pinned, so the pinned data won't move.
- // * If it returns a device, and so others may try to read this data, by
- // caller invariant, `attr` won't be released until the device is.
+ // * `soc_device_register_nodefer` only retains the attribute pointer when it
+ // returns a device. By caller invariant, `attr` won't be released until that
+ // device is unregistered.
let raw_soc = error::from_err_ptr(unsafe {
- bindings::soc_device_register(attr.as_mut_ptr())
+ bindings::soc_device_register_nodefer(attr.as_mut_ptr())
})?;
NonNull::new(raw_soc).ok_or(EINVAL)?
--
2.34.1
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-27 17:48 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 17:48 [PATCH 0/1] rust: soc: avoid dangling attributes when SoC bus is unavailable Priya Bala Govindasamy
2026-10-27 17:48 ` [PATCH 1/1] " Priya Bala Govindasamy
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox