Repository navigation
alloc: NumaNode::new() can allow safe Rust APIs to trigger a kernel NULL-pointer dereference #1263
Description
Problem
NumaNode::new() checks that the ID is in the range [0, MAX_NUMNODES), not whether the node is present or online:
Lines 126 to 132 in 969a0af
| pub fn new(node: i32) -> Result<Self> { | |
| // MAX_NUMNODES never exceeds 2**10 because NODES_SHIFT is 0..10. | |
| if node < 0 || node >= bindings::MAX_NUMNODES as i32 { | |
| return Err(EINVAL); | |
| } | |
| Ok(Self(node)) | |
| } |
The safe Rust allocator API can forward that ID to the underlying C allocator:
Lines 194 to 198 in 969a0af
| fn alloc(layout: Layout, flags: Flags, nid: NumaNode) -> Result<NonNull<[u8]>, AllocError> { | |
| // SAFETY: Passing `None` to `realloc` is valid by its safety requirements and asks for a | |
| // new memory allocation. | |
| unsafe { Self::realloc(None, layout, Layout::new::<()>(), flags, nid) } | |
| } |
linux/rust/kernel/alloc/allocator.rs
Lines 115 to 126 in 969a0af
| // SAFETY: | |
| // - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc` and thus only requires that | |
| // `ptr` is NULL or valid. | |
| // - `ptr` is either NULL or valid by the safety requirements of this function. | |
| // | |
| // GUARANTEE: | |
| // - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc`. | |
| // - Those functions provide the guarantees of this function. | |
| let raw_ptr = unsafe { | |
| // If `size == 0` and `ptr != NULL` the memory behind the pointer is freed. | |
| self.0(ptr.cast(), size, layout.align(), flags.0, nid.0).cast() | |
| }; |
On the configuration below, Kmalloc::alloc() with node 2 triggers a NULL-relative dereference in the page allocator, resulting in a GPF and kernel panic.
Reproducer
// SPDX-License-Identifier: GPL-2.0
//! Reproducer for a NULL-pointer dereference in the Rust kernel allocator.
//!
//! On a system where NUMA node 2 does not exist but its ID is below
//! MAX_NUMNODES, Kmalloc::alloc() can trigger a kernel crash.
use core::alloc::Layout;
use kernel::{
alloc::{allocator::Kmalloc, Allocator, NumaNode},
prelude::*,
};
module! {
type: RustMinimal,
name: "rust_minimal",
authors: ["User0"],
description: "Rust NUMA allocator crash reproducer",
license: "GPL",
}
struct RustMinimal;
impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
let node = 2;
let nid = NumaNode::new(node)?;
let layout = Layout::new::<[u8; 9]>();
pr_info!("NUMA reproducer: allocating on node {}\n", node);
match Kmalloc::alloc(layout, GFP_KERNEL, nid) {
Ok(ptr) => {
pr_info!("Allocation succeeded\n");
// SAFETY: The pointer was allocated by Kmalloc with this layout.
unsafe { Kmalloc::free(ptr.cast(), layout) };
}
Err(_) => pr_info!("Allocation failed\n"),
}
Ok(RustMinimal)
}
}The failing
Kmalloc::alloc()call is safe; theunsafefree is only on the successful path.
KASAN output
rust_minimal: NUMA reproducer: allocating on node 2
Oops: general protection fault, probably for non-canonical address 0xdffffc00000004a3
KASAN: probably user-memory-access in range [0x0000000000002518-0x000000000000251f]
RIP: __alloc_frozen_pages_noprof (include/linux/mmzone.h:1821,1852,1878; mm/page_alloc.c:5133,5421)
Call Trace:
new_slab (mm/slub.c:3347,3462,3513)
___slab_alloc (mm/slub.c:4568)
__kmalloc_node_track_caller_noprof (mm/slub.c:4987,5413,5545)
krealloc_node_align_noprof (mm/slub.c:6946,6967)
<rust_minimal::RustMinimal as kernel::module::Module>::init
(rust/kernel/alloc/allocator.rs:125,167;
rust/kernel/alloc.rs:191;
samples/rust/rust_minimal.rs:36)
Kernel panic - not syncing: Fatal exception
The fault at 0x2518 is consistent with dereferencing NODE_DATA(2) == NULL to access node_zonelists[1].
Full KASAN output
root@syzkaller:~# insmod rust_minimal.ko
[ 107.289604] audit: type=1400 audit(1791849546.428:8): avc: denied { module_load } for pid=237 comm="insmod" path="/root/rust_minimal.ko" dev="vda" 1
[ 107.294724] rust_minimal: loading out-of-tree module taints kernel.
[ 107.296342] rust_minimal: NUMA reproducer: allocating on node 2
[ 107.296369] Oops: general protection fault, probably for non-canonical address 0xdffffc00000004a3: 0000 [#1] SMP KASAN NOPTI
[ 107.299007] KASAN: probably user-memory-access in range [0x0000000000002518-0x000000000000251f]
[ 107.300284] CPU: 1 UID: 0 PID: 237 Comm: insmod Tainted: G O 7.3.0-rc3-00314-gcc7a2426cd3d-dirty #23 PREEMPT(lazy)
[ 107.302057] Tainted: [O]=OOT_MODULE
[ 107.302604] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014
[ 107.304305] RIP: 0010:__alloc_frozen_pages_noprof (include/linux/mmzone.h:1821 include/linux/mmzone.h:1852 include/linux/mmzone.h:1878 mm/page_alloc.c:5133 mm/page_alloc.c:5421)
[ 107.305198] Code: 24 68 8b 74 24 7c 48 85 d2 0f 85 df 01 00 00 4d 89 e6 45 89 ec 4d 8d 6f 08 4c 89 e8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <0f> b4
All code
========
0: 24 68 and $0x68,%al
2: 8b 74 24 7c mov 0x7c(%rsp),%esi
6: 48 85 d2 test %rdx,%rdx
9: 0f 85 df 01 00 00 jne 0x1ee
f: 4d 89 e6 mov %r12,%r14
12: 45 89 ec mov %r13d,%r12d
15: 4d 8d 6f 08 lea 0x8(%r15),%r13
19: 4c 89 e8 mov %r13,%rax
1c: 48 c1 e8 03 shr $0x3,%rax
20: 48 b9 00 00 00 00 00 movabs $0xdffffc0000000000,%rcx
27: fc ff df
2a:* 0f .byte 0xf <-- trapping instruction
2b: b4 .byte 0xb4
Code starting with the faulting instruction
===========================================
0: 0f .byte 0xf
1: b4 .byte 0xb4
[ 107.307964] RSP: 0018:ffff88811c5af440 EFLAGS: 00010216
[ 107.308765] RAX: 00000000000004a3 RBX: 1ffff110238b5e90 RCX: dffffc0000000000
[ 107.309833] RDX: 0000000000000000 RSI: 0000000000000002 RDI: 0000000000000000
[ 107.310909] RBP: ffff88811c5af550 R08: ffff88811c5af4c7 R09: 0000000000000000
[ 107.311971] R10: ffff88811c5af4b0 R11: ffffed10238b5e99 R12: 0000000000000000
[ 107.313073] R13: 0000000000002518 R14: 0000000000000000 R15: 0000000000002510
[ 107.314150] FS: 00007f5a24a98040(0000) GS:ffff888249344000(0000) knlGS:0000000000000000
[ 107.315342] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 107.316284] CR2: 00007f58c904125c CR3: 0000000115974000 CR4: 00000000000006f0
[ 107.317360] Call Trace:
[ 107.317758] <TASK>
[ 107.318105] ? irq_work_queue (kernel/irq_work.c:125)
[ 107.318738] ? __pfx___alloc_frozen_pages_noprof (mm/page_alloc.c:7118)
[ 107.319569] ? kernfs_xattr_get (fs/kernfs/inode.c:300)
[ 107.320187] new_slab (mm/slub.c:3347 mm/slub.c:3462 mm/slub.c:3513)
[ 107.320696] ___slab_alloc (mm/slub.c:4568)
[ 107.321265] __kmalloc_node_track_caller_noprof (mm/slub.c:4987 mm/slub.c:5413 mm/slub.c:5545)
[ 107.322144] ? <rust_minimal::RustMinimal as kernel::module::Module>::init (rust/kernel/alloc/allocator.rs:125 rust/kernel/alloc/allocator.rs:167 rust/kernel/alloc.rs:191 samples/rust/rust_minimal.rs:36) rust_minimal
[ 107.323953] ? <rust_minimal::RustMinimal as kernel::module::Module>::init (rust/kernel/alloc/allocator.rs:125 rust/kernel/alloc/allocator.rs:167 rust/kernel/alloc.rs:191 samples/rust/rust_minimal.rs:36) rust_minimal
[ 107.325780] ? <rust_minimal::RustMinimal as kernel::module::Module>::init (rust/kernel/alloc/allocator.rs:125 rust/kernel/alloc/allocator.rs:167 rust/kernel/alloc.rs:191 samples/rust/rust_minimal.rs:36) rust_minimal
[ 107.327609] krealloc_node_align_noprof (mm/slub.c:6946 mm/slub.c:6967)
[ 107.328340] ? rust_helper_krealloc_node_align (rust/helpers/slab.c:8)
[ 107.329149] <rust_minimal::RustMinimal as kernel::module::Module>::init (rust/kernel/alloc/allocator.rs:125 rust/kernel/alloc/allocator.rs:167 rust/kernel/alloc.rs:191 samples/rust/rust_minimal.rs:36) rust_minimal
[ 107.331028] ? __pfx__RNvXCskrgHf2WIdwv_12rust_minimalNtB2_11RustMinimalNtNtCs1F21stXub8a_6kernel6module6Module4init (??:?) rust_minimal
[ 107.332881] ? __pfx__RNvXs6_NtCs1F21stXub8a_6kernel3fmtINtB5_7AdapterRlENtNtCs1tTCFV2guGj_4core3fmt7Display3fmtCskrgHf2WIdwv_12rust_minimal+0x10/0x10]
[ 107.335114] ? unwind_next_frame (include/linux/rcupdate.h:882 include/linux/rcupdate.h:1216 arch/x86/kernel/unwind_orc.c:709)
[ 107.335808] ? deref_stack_reg (arch/x86/kernel/unwind_orc.c:422)
[ 107.336714] ? __pfx_init_module (??:?) rust_minimal
[ 107.338103] init_module (samples/rust/rust_minimal.rs:29) rust_minimal
[ 107.339320] ? do_one_initcall (arch/x86/include/asm/jump_label.h:37 include/trace/events/initcall.h:27 init/main.c:1361)
[ 107.340268] do_one_initcall (init/main.c:1362)
[ 107.341298] ? __pfx_init_module (??:?) rust_minimal
[ 107.342507] ? __pfx_do_one_initcall (??:?)
[ 107.343399] ? __kernel_text_address (kernel/extable.c:79)
[ 107.344273] ? unwind_get_return_address (arch/x86/kernel/unwind_orc.c:385)
[ 107.345193] ? __pfx_stack_trace_consume_entry (kernel/stacktrace.c:?)
[ 107.346005] ? arch_stack_walk (arch/x86/kernel/stacktrace.c:26)
[ 107.346607] ? _raw_spin_lock_irqsave (include/linux/instrumented.h:55 include/linux/atomic/atomic-instrumented.h:1301 include/asm-generic/qspinlock.h:112 include/linux/spinlock.h:188 include/linux/spinlock_api_smp.h:156 kernel/locking/spinlock.c:181)
[ 107.347289] ? __pfx__raw_spin_lock_irqsave (include/asm-generic/qspinlock.h:?)
[ 107.348055] ? stack_trace_save (kernel/stacktrace.c:122)
[ 107.348669] ? stack_depot_save_flags (lib/stackdepot.c:728)
[ 107.349372] ? kasan_save_track (mm/kasan/common.c:58 mm/kasan/common.c:78)
[ 107.350006] ? kasan_save_track (mm/kasan/common.c:57 mm/kasan/common.c:78)
[ 107.350620] ? __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415)
[ 107.351209] ? do_init_module (include/linux/slab.h:991 kernel/module/main.c:3105)
[ 107.351811] ? __se_sys_finit_module (kernel/module/main.c:3790 kernel/module/main.c:3802 kernel/module/main.c:3828 kernel/module/main.c:3812)
[ 107.352487] ? do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84)
[ 107.353103] ? entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 107.353879] ? kasan_unpoison (mm/kasan/shadow.c:146 mm/kasan/shadow.c:178)
[ 107.354455] ? __asan_register_globals (mm/kasan/generic.c:232 mm/kasan/generic.c:243)
[ 107.355161] do_init_module (kernel/module/main.c:3117)
[ 107.355755] ? __pfx_do_init_module (include/linux/srcutree.h:?)
[ 107.356408] __se_sys_finit_module (kernel/module/main.c:3790 kernel/module/main.c:3802 kernel/module/main.c:3828 kernel/module/main.c:3812)
[ 107.357108] ? __pfx___se_sys_finit_module (kernel/module/main.c:3812)
[ 107.357912] ? __pfx_vm_mmap_pgoff (include/linux/mmap_lock.h:?)
[ 107.358560] ? fput (fs/file_table.c:588)
[ 107.359031] ? fpregs_assert_state_consistent (arch/x86/kernel/fpu/core.c:915)
[ 107.359841] do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84)
[ 107.360411] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 107.361103] RIP: 0033:0x7f5a24b9a819
[ 107.361557] Code: 08 89 e8 5b 5d c3 66 2e 0f 1f 84 00 00 00 00 00 90 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> 38
All code
========
0: 08 89 e8 5b 5d c3 or %cl,-0x3ca2a418(%rcx)
6: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
d: 00 00 00
10: 90 nop
11: 48 89 f8 mov %rdi,%rax
14: 48 89 f7 mov %rsi,%rdi
17: 48 89 d6 mov %rdx,%rsi
1a: 48 89 ca mov %rcx,%rdx
1d: 4d 89 c2 mov %r8,%r10
20: 4d 89 c8 mov %r9,%r8
23: 4c 8b 4c 24 08 mov 0x8(%rsp),%r9
28: 0f 05 syscall
2a:* 48 rex.W <-- trapping instruction
2b: 38 .byte 0x38
Code starting with the faulting instruction
===========================================
0: 48 rex.W
1: 38 .byte 0x38
[ 107.363774] RSP: 002b:00007ffc5b3c5ba8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
[ 107.364756] RAX: ffffffffffffffda RBX: 00005586320a94e0 RCX: 00007f5a24b9a819
[ 107.365649] RDX: 0000000000000000 RSI: 00005585ff64d4a0 RDI: 0000000000000003
[ 107.366521] RBP: 00005585ff64d4a0 R08: 0000000000000000 R09: 00007f5a24c742a0
[ 107.367393] R10: 0000000000000003 R11: 0000000000000246 R12: 0000000000000000
[ 107.368344] R13: 0000000000000000 R14: 00005586320a94c0 R15: 0000000000000000
[ 107.369213] </TASK>
[ 107.369495] Modules linked in: rust_minimal(O+)
[ 107.370110] ---[ end trace 0000000000000000 ]---
[ 107.370743] RIP: 0010:__alloc_frozen_pages_noprof (include/linux/mmzone.h:1821 include/linux/mmzone.h:1852 include/linux/mmzone.h:1878 mm/page_alloc.c:5133 mm/page_alloc.c:5421)
[ 107.370755] Code: 24 68 8b 74 24 7c 48 85 d2 0f 85 df 01 00 00 4d 89 e6 45 89 ec 4d 8d 6f 08 4c 89 e8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <0f> b4
All code
========
0: 24 68 and $0x68,%al
2: 8b 74 24 7c mov 0x7c(%rsp),%esi
6: 48 85 d2 test %rdx,%rdx
9: 0f 85 df 01 00 00 jne 0x1ee
f: 4d 89 e6 mov %r12,%r14
12: 45 89 ec mov %r13d,%r12d
15: 4d 8d 6f 08 lea 0x8(%r15),%r13
19: 4c 89 e8 mov %r13,%rax
1c: 48 c1 e8 03 shr $0x3,%rax
20: 48 b9 00 00 00 00 00 movabs $0xdffffc0000000000,%rcx
27: fc ff df
2a:* 0f .byte 0xf <-- trapping instruction
2b: b4 .byte 0xb4
Code starting with the faulting instruction
===========================================
0: 0f .byte 0xf
1: b4 .byte 0xb4
[ 107.370761] RSP: 0018:ffff88811c5af440 EFLAGS: 00010216
[ 107.370768] RAX: 00000000000004a3 RBX: 1ffff110238b5e90 RCX: dffffc0000000000
[ 107.370773] RDX: 0000000000000000 RSI: 0000000000000002 RDI: 0000000000000000
[ 107.370778] RBP: ffff88811c5af550 R08: ffff88811c5af4c7 R09: 0000000000000000
[ 107.370782] R10: ffff88811c5af4b0 R11: ffffed10238b5e99 R12: 0000000000000000
[ 107.370786] R13: 0000000000002518 R14: 0000000000000000 R15: 0000000000002510
[ 107.370793] FS: 00007f5a24a98040(0000) GS:ffff888249344000(0000) knlGS:0000000000000000
[ 107.370799] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 107.370804] CR2: 00007f58c904125c CR3: 0000000115974000 CR4: 00000000000006f0
[ 107.370812] Kernel panic - not syncing: Fatal exception
[ 107.382093] Kernel Offset: 0x26800000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
Proposed fix
The following proposed patch rejects offline nodes at the shared allocator boundary, leaving NUMA_NO_NODE and zero-sized deallocations unchanged.
Proposed two-file patch
diff --git a/rust/helpers/mm.c b/rust/helpers/mm.c
index b5540997bd20..e3d9ac57214d 100644
--- a/rust/helpers/mm.c
+++ b/rust/helpers/mm.c
@@ -1,8 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/mm.h>
+#include <linux/nodemask.h>
#include <linux/sched/mm.h>
+__rust_helper
+bool rust_helper_node_online(int nid)
+{
+ return node_online(nid);
+}
+
__rust_helper void rust_helper_mmgrab(struct mm_struct *mm)
{
mmgrab(mm);
diff --git a/rust/kernel/alloc/allocator.rs b/rust/kernel/alloc/allocator.rs
index cd4203f27aed..8e50f1059743 100644
--- a/rust/kernel/alloc/allocator.rs
+++ b/rust/kernel/alloc/allocator.rs
@@ -101,6 +101,17 @@ unsafe fn call(
nid: NumaNode,
) -> Result<NonNull<[u8]>, AllocError> {
let size = layout.size();
+
+ // NumaNode::new validates the ID range, not whether the node is online.
+ // The C allocation paths require an online node for nonzero allocations.
+ // Keep NUMA_NO_NODE and zero-sized deallocations unchanged.
+ if size != 0 && nid != NumaNode::NO_NODE {
+ // SAFETY: The helper only reads the kernel's online-node mask.
+ if !unsafe { bindings::node_online(nid.0) } {
+ return Err(AllocError);
+ }
+ }
+
let ptr = match ptr {
Some(ptr) => {
if old_layout.size() == 0 {With the patch, the same reproducer reports:
rust_minimal: NUMA reproducer: allocating on node 2
rust_minimal: Allocation failed
The proposed patch prevents the reproduced crashes, but it has not been extensively evaluated and may require further review.
Environment
Reproduced on:
Kernel commit: cc7a2426cd3ddd1c6ef5b563c97ad9c43deab446
Architecture: x86_64, KASAN enabled
CONFIG_NUMA=y
CONFIG_NODES_SHIFT=6
/sys/devices/system/node/online: 0
/sys/devices/system/node/possible: 0
I may look into this further and submit a patch to the mailing list. In the meantime, anyone interested in taking this forward is very welcome to do so.
I have also notified the maintainers and reviewers by email.
Activity
Franklin-Qi commentedon Oct 16, 2026
Hello @GeorgeAndrou! I can work on this!
GeorgeAndrou commentedon Oct 16, 2026
@Franklin-Qi sounds good.
Remember to include:
Closes: https://github.com/Rust-for-Linux/linux/issues/1263Reported-by: Georgios Androutsopoulos <georgeandrout13@gmail.com>as well as the appropriate
Fixes:tag(s)