Linux driver-core infrastructure
 help / color / mirror / Atom feed
* [PATCH 0/1] rust: scatterlist: pin `Owned::sgt` to prevent invalidating `DmaMappedSgt::sgt`
@ 2026-11-11 18:41 Priya Bala Govindasamy
  2026-11-11 18:41 ` [PATCH 1/1] " Priya Bala Govindasamy
  0 siblings, 1 reply; 2+ messages in thread
From: Priya Bala Govindasamy @ 2026-11-11 18:41 UTC (permalink / raw)
  To: dakr, abdiel.janulgue, daniel.almeida, robin.murphy, a.hindborg,
	ojeda, boqun, gary, bjorn3_gh, lossin, aliceryhl, tmgross, tamird,
	acourbot, work, lyude
  Cc: driver-core, rust-for-linux, ardalan, zhiyunq, dzueck, pgovind2

Dear Linux kernel maintainers,

We are developing a tool called FerroLens to detect potential
unsound behavior 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/scatterlist.rs:

`Owned<P>` contains an indirect self-reference. During pinned
initialization, `DmaMappedSgt::sgt` is set to the address of `Owned::sgt`.
`DmaMappedSgt` is then owned through `Owned::dma`.

At the moment, only `Owned::dma` is marked as structurally pinned.
The `#[pin_data]` attribute therefore only considers `Devres<DmaMappedSgt>`
in its `Unpin` implementation.
Since `Devres<DmaMappedSgt>` implements `Unpin`, `Owned<P>`
is `Unpin` and can be moved.

Moving an `Owned<P>` instance could also move the `sgt` field.
This would cause `DmaMappedSgt::sgt` to now point at an invalid location.
Using this pointer would then lead to undefined behavior.

Below is a PoC kernel module that demonstrates the bug. It shows
safe code calling `Pin::into_inner()` on a
`Pin<KBox<SGTable<Owned<P>>>>` and then `KBox::into_inner()`, which moves
the value. `DmaMappedSgt::sgt` now points to a stale location.
When `DmaMappedSgt` is dropped, `dma_unmap_sgtable()` dereferences
the stale pointer, resulting in a use-after-free.

// SPDX-License-Identifier: GPL-2.0

use kernel::{
    bindings, device, dma, error, faux, page,
    prelude::*,
    scatterlist::{Owned, SGTable},
};

module! {
    type: ScatterListPoC,
    name: "scatterlist_poc",
    authors: ["Priya Govindasamy"],
    description: "ScatterList PoC kernel module",
    license: "GPL",
}

struct ScatterListPoC {
    _dev: faux::Registration,
}

impl kernel::Module for ScatterListPoC {
    fn init(_module: &'static ThisModule) -> Result<Self> {

        let dev = faux::Registration::new(c"scatterlist-poc", None)?;
        let generic_dev: &device::Device<device::Bound> = dev.as_ref().as_ref();

        let raw_dev = core::ptr::from_ref(generic_dev)
            .cast_mut()
            .cast::<bindings::device>();

        // A faux device does not provide a DMA mask. Point its streaming mask
        // at its coherent mask storage and configure both before creating any 
        // DMA mappings.
        //
        // SAFETY: `raw_dev` points to the registered faux device, whose
        // allocation is stable for the lifetime of `dev`.
        unsafe {
            (*raw_dev).dma_mask = core::ptr::addr_of_mut!((*raw_dev).coherent_dma_mask);
            error::to_result(bindings::dma_set_mask_and_coherent(raw_dev, u64::MAX))?;
        }

        let pages = VVec::<u8>::with_capacity(4 * page::PAGE_SIZE, GFP_KERNEL)?;
        let sgt: Pin<KBox<SGTable<Owned<VVec<u8>>>>> = KBox::pin_init(SGTable::new(
                generic_dev,
                pages,
                dma::DataDirection::Bidirectional,
                GFP_KERNEL,
            ),
            GFP_KERNEL
        )?;

        let boxed = Pin::into_inner(sgt);
        let table = KBox::into_inner(boxed);
        drop(table);

        Ok(Self {
            _dev: dev,
        })
    }
}

impl Drop for ScatterListPoC {
    fn drop(&mut self) {
        pr_info!("scatterlist_poc: module unloaded\n");
    }
}

Output:
[  550.191296] ==================================================================
[  550.191312] BUG: KASAN: slab-use-after-free in rust_helper_dma_unmap_sgtable+0x3d/0xa0
[  550.191357] Read of size 8 at addr ffff8882f4db57a8 by task insmod/5029
[  550.191370]
[  550.191399] CPU: 1 UID: 0 PID: 5029 Comm: insmod Tainted: G           OE    N   7.3.0+ #71 PREEMPT(lazy)
[  550.191416] Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE, [N]=TEST
[  550.191420] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1ubuntu2 04/01/2014
[  550.191430] Call Trace:
[  550.191439]  <TASK>
[  550.191459]  dump_stack_lvl+0x97/0xe0
[  550.191485]  print_report+0x175/0x6f0
[  550.191498]  ? __virt_addr_valid+0x18e/0x3b0
[  550.191515]  ? __virt_addr_valid+0x345/0x3b0
[  550.191525]  ? rust_helper_dma_unmap_sgtable+0x3d/0xa0
[  550.191533]  ? kasan_complete_mode_report_info+0x75/0x230
[  550.191546]  ? rust_helper_dma_unmap_sgtable+0x3d/0xa0
[  550.191553]  kasan_report+0xfa/0x130
[  550.191564]  ? rust_helper_dma_unmap_sgtable+0x3d/0xa0
[  550.191580]  __asan_report_load8_noabort+0x18/0x20
[  550.191588]  rust_helper_dma_unmap_sgtable+0x3d/0xa0
[  550.191599]  _RNvXCseDCAewP9wfK_15scatterlist_pocNtB2_14ScatterListPoCNtNtCsbuTvttuFvbr_6kernel6module6Module4init+0xac0/0xdf0 [scatterlist_poc]
[  550.191642]  init_module+0x15/0xff0 [scatterlist_poc]
[  550.191654]  do_one_initcall+0x119/0x4f0
[  550.191685]  ? do_init_module+0x63/0x740
[  550.191694]  ? __pfx_init_module+0x10/0x10 [scatterlist_poc]
[  550.191705]  ? __se_sys_finit_module+0x39d/0x5f0
[  550.191714]  ? __x64_sys_finit_module+0x7f/0x90
[  550.191723]  ? x64_sys_call+0xa66/0x2600
[  550.191732]  ? do_syscall_64+0x11c/0x3f0
[  550.191739]  ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  550.191747]  ? kvm_sched_clock_read+0x15/0x40
[  550.191763]  ? __lock_acquire+0xeeb/0x1040
[  550.191783]  ? kvm_sched_clock_read+0x15/0x40
[  550.191796]  ? debug_smp_processor_id+0x1b/0x30
[  550.191806]  ? _raw_spin_unlock_irqrestore+0x38/0x90
[  550.191815]  ? __this_cpu_preempt_check+0x17/0x20
[  550.191823]  ? lockdep_hardirqs_on+0xdc/0x160
[  550.191832]  ? debug_smp_processor_id+0x1b/0x30
[  550.191842]  ? kasan_save_alloc_info+0x3a/0x50
[  550.191852]  ? __kasan_kmalloc+0x94/0xb0
[  550.191863]  ? __kmalloc_cache_noprof+0x356/0x6c0
[  550.191876]  ? do_init_module+0x63/0x740
[  550.191885]  ? __kmalloc_cache_noprof+0x160/0x6c0
[  550.191899]  ? kasan_unpoison+0x4c/0x70
[  550.191914]  ? kasan_poison+0x44/0x60
[  550.191939]  do_init_module+0x23f/0x740
[  550.191975]  load_module+0x32d1/0x3f20
[  550.192009]  __se_sys_finit_module+0x39d/0x5f0
[  550.192045]  __x64_sys_finit_module+0x7f/0x90
[  550.192057]  x64_sys_call+0xa66/0x2600
[  550.192066]  do_syscall_64+0x11c/0x3f0
[  550.192072]  ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  550.192080]  ? __this_cpu_preempt_check+0x17/0x20
[  550.192088]  ? lockdep_hardirqs_on+0xdc/0x160
[  550.192095]  ? do_syscall_64+0x217/0x3f0
[  550.192108]  ? debug_smp_processor_id+0x1b/0x30
[  550.192118]  ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  550.192125]  ? __this_cpu_preempt_check+0x17/0x20
[  550.192132]  ? lockdep_hardirqs_on+0xdc/0x160
[  550.192140]  ? do_syscall_64+0x217/0x3f0
[  550.192148]  ? __this_cpu_preempt_check+0x17/0x20
[  550.192160]  ? debug_smp_processor_id+0x1b/0x30
[  550.192171]  ? debug_smp_processor_id+0x1b/0x30
[  550.192177]  ? rcu_is_watching+0x1a/0xc0
[  550.192190]  ? trace_irq_disable+0x3c/0x130
[  550.192205]  ? do_syscall_64+0x6d/0x3f0
[  550.192211]  ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  550.192222]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  550.192238] RIP: 0033:0x7fdbc511e9dd
[  550.192259] 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
[  550.192270] RSP: 002b:00007ffd59690678 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
[  550.192292] RAX: ffffffffffffffda RBX: 000055627cd6e7d0 RCX: 00007fdbc511e9dd
[  550.192298] RDX: 0000000000000000 RSI: 000055627be2ccd2 RDI: 0000000000000003
[  550.192303] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
[  550.192308] R10: 0000000000000003 R11: 0000000000000246 R12: 000055627be2ccd2
[  550.192312] R13: 000055627cd6e790 R14: 000055627be2b888 R15: 000055627cd6e8e0
[  550.192332]  </TASK>
[  550.192336]
[  550.192338] Allocated by task 5029:
[  550.192480]
[  550.192482] Freed by task 5029:
[  550.192597]
[  550.192600] The buggy address belongs to the object at ffff8882f4db5780
[  550.192600]  which belongs to the cache kmalloc-64 of size 64
[  550.192613] The buggy address is located 40 bytes inside of
[  550.192613]  freed 64-byte region [ffff8882f4db5780, ffff8882f4db57c0)
[  550.192620]
[  550.192623] The buggy address belongs to the physical page:
[  550.192713]
[  550.192716] Memory state around the buggy address:
[  550.192725]  ffff8882f4db5680: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[  550.192730]  ffff8882f4db5700: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[  550.192734] >ffff8882f4db5780: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[  550.192738]                                   ^
[  550.192743]  ffff8882f4db5800: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[  550.192747]  ffff8882f4db5880: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[  550.192751] ==================================================================

Priya Bala Govindasamy (1):
  rust: scatterlist: pin `Owned::sgt` to prevent invalidating
    `DmaMappedSgt::sgt`

 rust/kernel/scatterlist.rs | 3 +++
 1 file changed, 3 insertions(+)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH 1/1] rust: scatterlist: pin `Owned::sgt` to prevent invalidating `DmaMappedSgt::sgt`
  2026-11-11 18:41 [PATCH 0/1] rust: scatterlist: pin `Owned::sgt` to prevent invalidating `DmaMappedSgt::sgt` Priya Bala Govindasamy
@ 2026-11-11 18:41 ` Priya Bala Govindasamy
  0 siblings, 0 replies; 2+ messages in thread
From: Priya Bala Govindasamy @ 2026-11-11 18:41 UTC (permalink / raw)
  To: dakr, abdiel.janulgue, daniel.almeida, robin.murphy, a.hindborg,
	ojeda, boqun, gary, bjorn3_gh, lossin, aliceryhl, tmgross, tamird,
	acourbot, work, lyude
  Cc: driver-core, rust-for-linux, ardalan, zhiyunq, dzueck, pgovind2

`Owned<P>` contains an indirect self-reference. During pinned
initialization, `DmaMappedSgt::sgt` is set to the address of `Owned::sgt`.
`DmaMappedSgt` is then owned through `Owned::dma`.

At the moment, only `Owned::dma` is marked as structurally pinned.
The `#[pin_data]` attribute therefore only considers `Devres<DmaMappedSgt>`
in its `Unpin` implementation.
Since `Devres<DmaMappedSgt>` implements `Unpin`, `Owned<P>`
is `Unpin` and can be moved.

Moving an `Owned<P>` instance could also move the `sgt` field.
This would cause `DmaMappedSgt::sgt` to now point at an invalid location.
Using this pointer would then lead to undefined behavior.

Fix this by structurally pinning `Owned::sgt`. Since `RawSGTable`
internally uses a `PhantomPinned` marker, it is `!Unpin`. This change
ensures that `Owned<P>` is also `!Unpin`.

This issue was detected by FerroLens, an LLM-assisted analysis tool, and
was then manually analyzed and reproduced.

Fixes: 05aa6fb1c21d ("rust: scatterlist: Add abstraction for sg_table")
Reported-by: Dylan Zueck <dzueck@uci.edu>
Signed-off-by: Priya Bala Govindasamy <pgovind2@uci.edu>
---
 rust/kernel/scatterlist.rs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/rust/kernel/scatterlist.rs b/rust/kernel/scatterlist.rs
index 3a5250f85000..afa2640061f1 100644
--- a/rust/kernel/scatterlist.rs
+++ b/rust/kernel/scatterlist.rs
@@ -318,11 +318,14 @@ pub struct Owned<P> {
     // `struct sg_table` and finally free the backing pages.
     #[pin]
     dma: Devres<DmaMappedSgt>,
+    #[pin]
     sgt: RawSGTable,
     _pages: P,
 }
 
 // SAFETY: `Owned` can be sent to any task if `P` can be send to any task.
+// Additionally, `Owned` can only be constructed pinned, and `sgt` is structurally
+// pinned, so transferring ownership cannot invalidate `DmaMappedSgt::sgt`.
 unsafe impl<P: Send> Send for Owned<P> {}
 
 // SAFETY: `Owned` has no interior mutability and can be accessed concurrently if `P` can be
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-11 18:41 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 18:41 [PATCH 0/1] rust: scatterlist: pin `Owned::sgt` to prevent invalidating `DmaMappedSgt::sgt` Priya Bala Govindasamy
2026-11-11 18:41 ` [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