Skip to content

pub fn copy_from_iter_raw incorrectly elided output lifetime #1265

Open

Description

@spidermana

Hi,

There is an unsound problem in rust/kernel/iov.rs:164.

The pub function called copy_from_iter_raw returns &mut [u8] outlives out buffer (elision binds to &mut self).

Config

  • kernel tag: latest

  • kbuild:

    ./scripts/config --enable RUST --enable SAMPLES --enable SAMPLES_RUST
    --enable SAMPLE_RUST_POC_IOV --enable KASAN

Root Cause

Based on lifetime elision rules, the signature pub fn copy_from_iter_raw(&mut self, out: &mut [MaybeUninit<u8>]) -> &mut [u8] (in rust/kernel/iov.rs) elides the output lifetime to that of &mut self , not out.

linux/rust/kernel/iov.rs

Lines 164 to 179 in 7c306f0

pub fn copy_from_iter_raw(&mut self, out: &mut [MaybeUninit<u8>]) -> &mut [u8] {
let capacity = out.len();
let out = out.as_mut_ptr().cast::<u8>();
// GUARANTEES: The C API guarantees that it does not write uninitialized bytes to the
// provided buffer.
// SAFETY:
// * By the type invariants, it is still valid to read from this IO vector.
// * `out` is valid for writing for `capacity` bytes because it comes from a slice of
// that length.
let len = unsafe { bindings::_copy_from_iter(out.cast(), capacity, self.as_raw()) };
// SAFETY: The underlying C api guarantees that initialized bytes have been written to the
// first `len` bytes of the spare capacity.
unsafe { slice::from_raw_parts_mut(out, len) }
}

If I understand correctly, the returned slice is built via slice::from_raw_parts_mut from a raw pointer derived from out, so it aliases/points into out's memory, yet the borrow checker only ties it to self.

Safe code can drop or reuse out while the returned &mut [u8] is still live, giving a dangling reference (UAF) and overlapping &mut aliasing.

The function is a pub one on a kernel crate in linux, so any safe caller can trigger it. In-tree callers only use .len() so are not exploitable for now, but the API is unsound.

POC

The poc is built as kernel module. I have (dirty-)patched the following things in tested kernel source code

  • config SAMPLE_RUST_POC_IOV in samples/rust/Kconfig
  • obj-$(CONFIG_SAMPLE_RUST_POC_IOV) += rust_poc_iov.o in samples/rust/Makefile
  • a poc module below located at samples/rust/rust_poc_iov.rs
// SPDX-License-Identifier: GPL-2.0

//! PoC for the lifetime-elision hole in `IovIterSource::copy_from_iter_raw`
//! (`rust/kernel/iov.rs`).
//!
//! Build with `CONFIG_KASAN=y`; 

use core::mem::MaybeUninit;
use kernel::{bindings, iov, prelude::*};

module! {
    type: PocIov,
    name: "rfl_poc_iov",
    authors: ["spidermana"],
    description: "PoC: copy_from_iter_raw() returns a slice tied to &mut self, not to `out`",
    license: "GPL",
}

struct PocIov;

impl kernel::Module for PocIov {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        match poc() {
            Ok(()) => pr_info!("POC: DONE\n"),
            Err(e) => pr_info!("POC: SKIPPED (setup failed: {:?})\n", e),
        }
        // Never fail init: the verdict is in the log, and a failed built-in initcall only adds
        // noise.
        Ok(PocIov)
    }
}

fn poc() -> Result {
    // Setup (C API): an ITER_SOURCE `iov_iter` reading from a buffer that outlives `it`.
    let src = [0x41u8; 32];
    let kv = bindings::kvec {
        iov_base: src.as_ptr().cast_mut().cast(),
        iov_len: src.len(),
    };
    let mut raw = MaybeUninit::<bindings::iov_iter>::zeroed();
    // SAFETY: `raw` is writable and `kv` describes one valid segment covering `src`.
    unsafe { bindings::iov_iter_kvec(raw.as_mut_ptr(), bindings::ITER_SOURCE, &kv, 1, src.len()) };
    // SAFETY: `raw` now holds a valid `ITER_SOURCE` iterator over `src`, which outlives `it`,
    // and nothing else touches `raw`.
    let it: &mut iov::IovIterSource<'_> = unsafe { iov::IovIterSource::from_raw(raw.as_mut_ptr()) };

    // =========================== SAFE RUST ===========================
    let dangling: &mut [u8] = {
        let mut buf = KVec::<u8>::with_capacity(32, GFP_KERNEL)?;
        it.copy_from_iter_raw(buf.spare_capacity_mut()) // <-- the hole: result is tied to `it`
        // `buf` (a 32-byte slab object) is kfree'd right here
    };
    pr_info!(
        "POC: holding &mut [u8] of len {} whose backing allocation is already freed\n",
        dangling.len()
    );
    pr_info!("POC: touching the dangling slice (write)\n");
    dangling.fill(0x42); // use-after-free write
    // =================================================================
    Ok(())
}

Here is a part of logs from KASAN:

[    2.370972] BUG: KASAN: slab-use-after-free in _RNvXCs62u9c2E79YJ_12rust_poc_iovNtB2_6PocIovNtNtCs1EKtwoKEMO2_6kernel6module6Module4init+0x174/0x258
[    2.371734] Write of size 32 at addr ffff00000c1e1a80 by task swapper/0/1
[    2.371802] 
[    2.372500] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.3.0-rc4-00001-g9a879651bb7f-dirty #90 PREEMPT 
[    2.372658] Hardware name: linux,dummy-virt (DT)
[    2.372890] Call trace:
[    2.373068]  show_stack+0x18/0x24 (C)
[    2.375312]  __dump_stack+0x28/0x38
[    2.375412]  dump_stack_lvl+0x54/0x6c
[    2.375448]  print_address_description+0x98/0x218
[    2.375484]  print_report+0x68/0x84
[    2.375529]  kasan_report+0xb0/0x10c
[    2.375566]  kasan_check_range+0x264/0x2a4
[    2.375596]  __asan_memset+0x34/0x64
[    2.375629]  _RNvXCs62u9c2E79YJ_12rust_poc_iovNtB2_6PocIovNtNtCs1EKtwoKEMO2_6kernel6module6Module4init+0x174/0x258

Fix

pub fn copy_from_iter_raw<'a>(&mut self, out: &'a mut [MaybeUninit<u8>]) -> &'a mut [u8]

Others

If this fix is confirmed to be necessary, I can send a patch email to the LKML.

Always open to suggestions!

Activity

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

    @ojeda
    Member

    Cc @Darksonn since she originally wrote the code

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