Skip to content

alloc: NumaNode::new() can allow safe Rust APIs to trigger a kernel NULL-pointer dereference #1263

Open

Description

@GeorgeAndrou

Problem

NumaNode::new() checks that the ID is in the range [0, MAX_NUMNODES), not whether the node is present or online:

linux/rust/kernel/alloc.rs

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:

linux/rust/kernel/alloc.rs

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) }
}

// 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; the unsafe free 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.

Cc: @dakr @ojeda @Darksonn

I have also notified the maintainers and reviewers by email.

Activity

  1. added
    • libRelated to the `rust/` library.
    unsoundThe possibility of UB in safe code.
    on Oct 13, 2026
  2. Franklin-Qi commented on Oct 16, 2026

    @Franklin-Qi

    Hello @GeorgeAndrou! I can work on this!

  3. GeorgeAndrou commented on Oct 16, 2026

    @GeorgeAndrou
    Author

    @Franklin-Qi sounds good.

    Remember to include:
    Closes: https://github.com/Rust-for-Linux/linux/issues/1263
    Reported-by: Georgios Androutsopoulos <georgeandrout13@gmail.com>

    as well as the appropriate Fixes: tag(s)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    unsoundThe possibility of UB in safe code.• libRelated to the `rust/` library.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions