Skip to content

Commit d00f4c4

Browse files
author
Sven Van Asbroeck
committed
rust: chrdev: fix potential use-after-free
The kernel's `struct cdev` is a reference-counted `kobject`. This means that the object isn't guaranteed to be cleaned up after a call to `cdev_del` - the cleanup may occur later. Rust's `chrdev` places the `struct cdev` in memory owned by the module. On module unload, it calls `cdev_del` and releases all module memory, including the `struct cdev`. But that structure might only be cleaned up later - resulting in a potential use-after- free. This issue is reliably triggered using CONFIG_DEBUG_KOBJECT_RELEASE, which has been developed specifically to catch this subtle class of bugs. Fix by allocating the `cdev` using `cdev_alloc`, which stores the object on the kernel's `kalloc` heap. Now it can outlive the module, and be cleaned up+released when the kernel decides it's time. Signed-off-by: Sven Van Asbroeck <[email protected]>
1 parent 7e0815b commit d00f4c4

File tree

1 file changed

+59
-24
lines changed

1 file changed

+59
-24
lines changed

rust/kernel/chrdev.rs

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use alloc::boxed::Box;
1212
use core::convert::TryInto;
1313
use core::marker::PhantomPinned;
14-
use core::mem::MaybeUninit;
1514
use core::pin::Pin;
1615

1716
use crate::bindings;
@@ -20,10 +19,57 @@ use crate::error::{Error, KernelResult};
2019
use crate::file_operations;
2120
use crate::types::CStr;
2221

22+
/// Character device.
23+
///
24+
/// # Invariants
25+
///
26+
/// The pointer [`Cdev::cdev`] is valid and non-null.
27+
struct Cdev(*mut bindings::cdev);
28+
29+
impl Cdev {
30+
fn alloc(
31+
fops: &'static bindings::file_operations,
32+
module: &'static crate::ThisModule,
33+
) -> KernelResult<Self> {
34+
// SAFETY: `bindings::cdev_alloc` returns a valid pointer or null.
35+
let cdev = unsafe { bindings::cdev_alloc() };
36+
if cdev.is_null() {
37+
return Err(Error::ENOMEM);
38+
}
39+
// SAFETY: [`Cdev::cdev`] is valid and non-null.
40+
// `fops` and `module` have `'static` lifetime.
41+
unsafe {
42+
(*cdev).ops = fops;
43+
(*cdev).owner = module.0;
44+
}
45+
// INVARIANTS: [`Cdev::cdev`] is guaranteed valid and non-null.
46+
Ok(Self(cdev))
47+
}
48+
49+
fn add(&mut self, dev: bindings::dev_t, count: c_types::c_uint) -> KernelResult<()> {
50+
// SAFETY: [`Cdev::cdev`] is valid and non-null.
51+
// dev and count are scalars.
52+
let rc = unsafe { bindings::cdev_add(self.0, dev, count) };
53+
if rc != 0 {
54+
return Err(Error::from_kernel_errno(rc));
55+
}
56+
Ok(())
57+
}
58+
}
59+
60+
impl Drop for Cdev {
61+
fn drop(&mut self) {
62+
// SAFETY: [`Cdev::cdev`] is guaranteed valid and non-null.
63+
unsafe {
64+
bindings::cdev_del(self.0);
65+
}
66+
}
67+
}
68+
2369
struct RegistrationInner<const N: usize> {
2470
dev: bindings::dev_t,
2571
used: usize,
26-
cdevs: [MaybeUninit<bindings::cdev>; N],
72+
cdevs: [Option<Cdev>; N],
2773
_pin: PhantomPinned,
2874
}
2975

@@ -96,10 +142,11 @@ impl<const N: usize> Registration<{ N }> {
96142
if res != 0 {
97143
return Err(Error::from_kernel_errno(res));
98144
}
145+
const NONE: Option<Cdev> = None;
99146
this.inner = Some(RegistrationInner {
100147
dev,
101148
used: 0,
102-
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
149+
cdevs: [NONE; N],
103150
_pin: PhantomPinned,
104151
});
105152
}
@@ -108,22 +155,13 @@ impl<const N: usize> Registration<{ N }> {
108155
if inner.used == N {
109156
return Err(Error::EINVAL);
110157
}
111-
let cdev = inner.cdevs[inner.used].as_mut_ptr();
112-
// SAFETY: Calling unsafe functions and manipulating `MaybeUninit`
113-
// pointer.
114-
unsafe {
115-
bindings::cdev_init(
116-
cdev,
117-
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
118-
// registration.
119-
file_operations::FileOperationsVtable::<Self, T>::build(),
120-
);
121-
(*cdev).owner = this.this_module.0;
122-
let rc = bindings::cdev_add(cdev, inner.dev + inner.used as bindings::dev_t, 1);
123-
if rc != 0 {
124-
return Err(Error::from_kernel_errno(rc));
125-
}
126-
}
158+
159+
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
160+
// registration.
161+
let fops = unsafe { file_operations::FileOperationsVtable::<Self, T>::build() };
162+
let mut cdev = Cdev::alloc(fops, &this.this_module)?;
163+
cdev.add(inner.dev + inner.used as bindings::dev_t, 1)?;
164+
inner.cdevs[inner.used].replace(cdev);
127165
inner.used += 1;
128166
Ok(())
129167
}
@@ -149,12 +187,9 @@ unsafe impl<const N: usize> Sync for Registration<{ N }> {}
149187
impl<const N: usize> Drop for Registration<{ N }> {
150188
fn drop(&mut self) {
151189
if let Some(inner) = self.inner.as_mut() {
152-
// SAFETY: Calling unsafe functions, `0..inner.used` of
153-
// `inner.cdevs` are initialized in `Registration::register`.
190+
// SAFETY: TODO needs explanation why unregister_chrdev_region
191+
// is safe here.
154192
unsafe {
155-
for i in 0..inner.used {
156-
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
157-
}
158193
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
159194
}
160195
}

0 commit comments

Comments
 (0)