From: Priya Bala Govindasamy <pgovind2@uci.edu>
To: a.hindborg@kernel.org, ojeda@kernel.org
Cc: boqun@kernel.org, gary@garyguo.net, bjorn3_gh@protonmail.com,
lossin@kernel.org, aliceryhl@google.com, tmgross@umich.edu,
dakr@kernel.org, daniel.almeida@collabora.com, tamird@kernel.org,
acourbot@nvidia.com, work@onurozkan.dev,
rust-for-linux@vger.kernel.org, ardalan@uci.edu,
zhiyunq@cs.ucr.edu, dzueck@uci.edu, pgovind2@uci.edu
Subject: [PATCH 0/1] rust: configfs: Fix reference creation from uninitialized data in `Attribute::show`
Date: Thu, 8 Oct 2026 23:42:13 +0000 [thread overview]
Message-ID: <cover.1790981219.git.pgovind2@uci.edu> (raw)
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/configfs.rs:
When a configfs attribute is written and then read through the
same open file, the read reuses the buffer allocated for the write.
This buffer is not zero-initialized. Writes to the attribute go
through `fill_write_buffer()`, which allocates
the buffer with `kmalloc`, and initializes only the input bytes
and a trailing NUL byte.
The read in `Attribute::show` callback creates an `&mut [u8; PAGE_SIZE]`
to the buffer. This is unsound because the reference may include
uninitialized bytes.
Here is a kernel module and a python script that interacts with it
to demonstrate the bug:
// SPDX-License-Identifier: GPL-2.0
//! Rust configfs sample.
use kernel::alloc::flags;
use kernel::configfs;
use kernel::configfs::configfs_attrs;
use kernel::new_mutex;
use kernel::page::PAGE_SIZE;
use kernel::prelude::*;
use kernel::sync::Mutex;
module! {
type: RustConfigfs,
name: "rust_configfs",
authors: ["Rust for Linux Contributors"],
description: "Rust configfs sample",
license: "GPL",
}
#[pin_data]
struct RustConfigfs {
#[pin]
config: configfs::Subsystem<Configuration>,
}
#[pin_data]
struct Configuration {
message: &'static CStr,
#[pin]
bar: Mutex<(KBox<[u8; PAGE_SIZE]>, usize)>,
}
impl Configuration {
fn new() -> impl PinInit<Self, Error> {
try_pin_init!(Self {
message: c"Hello World\n",
bar <- new_mutex!((KBox::new([0; PAGE_SIZE], flags::GFP_KERNEL)?, 0)),
})
}
}
impl kernel::InPlaceModule for RustConfigfs {
fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
pr_info!("Rust configfs sample (init)\n");
// Define a subsystem with the data type `Configuration`, two
// attributes, `message` and `bar` and child group type `Child`. `mkdir`
// in the directory representing this subsystem will create directories
// backed by the `Child` type.
let item_type = configfs_attrs! {
container: configfs::Subsystem<Configuration>,
data: Configuration,
child: Child,
attributes: [
message: 0,
bar: 1,
],
};
try_pin_init!(Self {
config <- configfs::Subsystem::new(
c"rust_configfs", item_type, Configuration::new()
),
})
}
}
#[vtable]
impl configfs::GroupOperations for Configuration {
type Child = Child;
fn make_group(&self, name: &CStr) -> Result<impl PinInit<configfs::Group<Child>, Error>> {
// Define a group with data type `Child`, one attribute `baz` and child
// group type `GrandChild`. `mkdir` in the directory representing this
// group will create directories backed by the `GrandChild` type.
let tpe = configfs_attrs! {
container: configfs::Group<Child>,
data: Child,
child: GrandChild,
attributes: [
baz: 0,
],
};
Ok(configfs::Group::new(name.try_into()?, tpe, Child::new()))
}
}
#[vtable]
impl configfs::AttributeOperations<0> for Configuration {
type Data = Configuration;
fn show(container: &Configuration, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
pr_info!("Show message\n");
let data = container.message.to_bytes();
page[0..data.len()].copy_from_slice(data);
Ok(data.len())
}
}
#[vtable]
impl configfs::AttributeOperations<1> for Configuration {
type Data = Configuration;
fn show(container: &Configuration, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
pr_info!(
"Show bar: page addr = {:p}, capacity = {} bytes\n",
page.as_ptr(),
page.len()
);
let is_zeroed = page.iter().all(|&b| b == 0);
pr_info!("Show bar: page is zero-initialized: {}\n", is_zeroed);
pr_info!("Show bar: first 8 initial bytes = {:x?}\n", &page[..8]);
let guard = container.bar.lock();
let data = guard.0.as_slice();
let len = guard.1;
if len > PAGE_SIZE {
return Err(kernel::error::code::EINVAL);
}
page[..len].copy_from_slice(&data[..len]);
// 4. Verify post-write state
pr_info!("Show bar: wrote {} bytes into page\n", len);
Ok(len)
}
fn store(container: &Configuration, page: &[u8]) -> Result {
pr_info!(
"Store bar: container={:#x} bar={:#x}\n",
container as *const Configuration as usize,
&container.bar as *const _ as usize,
);
let mut guard = container.bar.lock();
pr_info!(
"Store bar: interpreted buf={:#x} old_len={}\n",
guard.0.as_slice().as_ptr() as usize,
guard.1
);
guard.0[0..page.len()].copy_from_slice(page);
guard.1 = page.len();
Ok(())
}
}
// `pin_data` cannot handle structs without braces.
#[pin_data]
struct Child {}
impl Child {
fn new() -> impl PinInit<Self, Error> {
try_pin_init!(Self {})
}
}
#[vtable]
impl configfs::GroupOperations for Child {
type Child = GrandChild;
fn make_group(&self, name: &CStr) -> Result<impl PinInit<configfs::Group<GrandChild>, Error>> {
// Define a group with data type `GrandChild`, one attribute `gc`. As no
// child type is specified, it will not be possible to create subgroups
// in this group, and `mkdir`in the directory representing this group
// will return an error.
let tpe = configfs_attrs! {
container: configfs::Group<GrandChild>,
data: GrandChild,
attributes: [
gc: 0,
],
};
Ok(configfs::Group::new(
name.try_into()?,
tpe,
GrandChild::new(),
))
}
}
#[vtable]
impl configfs::AttributeOperations<0> for Child {
type Data = Child;
fn show(_container: &Child, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
pr_info!("Show baz\n");
let data = c"Hello Baz\n".to_bytes();
page[0..data.len()].copy_from_slice(data);
Ok(data.len())
}
}
// `pin_data` cannot handle structs without braces.
#[pin_data]
struct GrandChild {}
impl GrandChild {
fn new() -> impl PinInit<Self, Error> {
try_pin_init!(Self {})
}
}
#[vtable]
impl configfs::AttributeOperations<0> for GrandChild {
type Data = GrandChild;
fn show(_container: &GrandChild, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
pr_info!("Show grand child\n");
let data = c"Hello GC\n".to_bytes();
page[0..data.len()].copy_from_slice(data);
Ok(data.len())
}
}
poc.py:
```
import os
fd = os.open("/sys/kernel/config/rust_configfs/bar", os.O_RDWR)
try:
os.write(fd, b"x")
os.lseek(fd, 0, os.SEEK_SET)
print(repr(os.read(fd, 4096)))
finally:
os.close(fd)
```
output:
[ 257.079636] rust_configfs: Rust configfs sample (init)
[ 269.778979] rust_configfs: Store bar: container=0xffffffffa0740100 bar=0xffffffffa0740110
[ 269.779273] rust_configfs: Store bar: interpreted buf=0xffff888129ce4000 old_len=0
[ 269.779332] rust_configfs: Show bar: page addr = 0x00000000fca4e938, capacity = 4096 bytes
[ 269.779352] rust_configfs: Show bar: page is zero-initialized: false
[ 269.779358] rust_configfs: Show bar: first 8 initial bytes = [78, 0, fb, 4f, 83, 88, ff, ff]
[ 269.779374] rust_configfs: Show bar: wrote 1 bytes into page
This output demonstrates that the bytes of the page that have not
been written to by `Attribute::store` are not zero-initialized,
which is UB when creating a reference to it.
Priya Bala Govindasamy (1):
rust: configfs: Fix reference creation from uninitialized data in
`Attribute::show`
rust/kernel/configfs.rs | 3 +++
1 file changed, 3 insertions(+)
--
2.34.1
next reply other threads:[~2026-10-08 23:42 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <cUqSvTsTaMYu_TRrAABCYTNz0HZf5JeFdIq_Vv4e_-GFMgI5NwVfZJDcy8CTgc4fH_1OoiiMg2I2LIzQX1y3Rg==@protonmail.internalid>
2026-10-08 23:42 ` Priya Bala Govindasamy [this message]
2026-10-08 23:42 ` [PATCH] rust: configfs: Fix reference creation from uninitialized data in `Attribute::show` Priya Bala Govindasamy
[not found] ` <87v77bcg0m.fsf@kernel.org>
2026-10-09 12:49 ` [PATCH 0/1] " Andreas Hindborg
2026-10-09 12:54 ` Gary Guo
[not found] ` <f-SD-RJDAM9vxqOj3MupDr-geggiu7qcKsYBwlTK02DS6siKkRU643dmvGb-ihkpH5LKF6zW1DH2KHAmzXjvNg==@protonmail.internalid>
[not found] ` <CAPPBnEZBeB2cZ7_2Syt_Vy0HcXWrRF6j-M1HmZQwKkwG=1nz-Q@mail.gmail.com>
2026-10-09 20:17 ` Andreas Hindborg
2026-10-09 20:32 ` Andreas Hindborg
2026-10-09 22:15 ` Gary Guo
2026-10-09 21:19 ` Priya Bala Govindasamy
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1790981219.git.pgovind2@uci.edu \
--to=pgovind2@uci.edu \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=ardalan@uci.edu \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dzueck@uci.edu \
--cc=gary@garyguo.net \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
--cc=zhiyunq@cs.ucr.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox