Skip to content

Commit 5d9e66d

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 5d9e66d

File tree

1 file changed

+66
-24
lines changed

1 file changed

+66
-24
lines changed

rust/kernel/chrdev.rs

Lines changed: 66 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,64 @@ use crate::error::{Error, KernelResult};
2019
use crate::file_operations;
2120
use crate::types::CStr;
2221

22+
/// Character device.
23+
///
24+
/// # Invariants
25+
///
26+
/// - [`self.0`] is valid and non-null.
27+
/// - [`(*self.0).ops`] is valid and has static lifetime.
28+
/// - [`(*self.0).owner`] is valid and has static lifetime.
29+
struct Cdev(*mut bindings::cdev);
30+
31+
impl Cdev {
32+
fn alloc(
33+
fops: &'static bindings::file_operations,
34+
module: &'static crate::ThisModule,
35+
) -> KernelResult<Self> {
36+
// SAFETY: FFI call.
37+
let cdev = unsafe { bindings::cdev_alloc() };
38+
if cdev.is_null() {
39+
return Err(Error::ENOMEM);
40+
}
41+
// SAFETY: `cdev` is valid and non-null since `cdev_alloc()`
42+
// returned a valid pointer which was null-checked.
43+
unsafe {
44+
(*cdev).ops = fops;
45+
(*cdev).owner = module.0;
46+
}
47+
// INVARIANTS: [`self.0`] is valid and non-null.
48+
// [`(*self.0).ops`] and [`(*self.0).owner`] are valid and have
49+
// static lifetime.
50+
Ok(Self(cdev))
51+
}
52+
53+
fn add(&mut self, dev: bindings::dev_t, count: c_types::c_uint) -> KernelResult<()> {
54+
// SAFETY: [`self.0`] is valid and non-null by the type invariants.
55+
// [`(*self.0).ops`] and [`(*self.0).owner`] are valid and have static
56+
// lifetime by the type invariants, which guarantees that they will
57+
// live as least as long as [`self.0`].
58+
// dev and count are scalars.
59+
let rc = unsafe { bindings::cdev_add(self.0, dev, count) };
60+
if rc != 0 {
61+
return Err(Error::from_kernel_errno(rc));
62+
}
63+
Ok(())
64+
}
65+
}
66+
67+
impl Drop for Cdev {
68+
fn drop(&mut self) {
69+
// SAFETY: [`self.0`] is valid and non-null by the type invariants.
70+
unsafe {
71+
bindings::cdev_del(self.0);
72+
}
73+
}
74+
}
75+
2376
struct RegistrationInner<const N: usize> {
2477
dev: bindings::dev_t,
2578
used: usize,
26-
cdevs: [MaybeUninit<bindings::cdev>; N],
79+
cdevs: [Option<Cdev>; N],
2780
_pin: PhantomPinned,
2881
}
2982

@@ -96,10 +149,11 @@ impl<const N: usize> Registration<{ N }> {
96149
if res != 0 {
97150
return Err(Error::from_kernel_errno(res));
98151
}
152+
const NONE: Option<Cdev> = None;
99153
this.inner = Some(RegistrationInner {
100154
dev,
101155
used: 0,
102-
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
156+
cdevs: [NONE; N],
103157
_pin: PhantomPinned,
104158
});
105159
}
@@ -108,22 +162,13 @@ impl<const N: usize> Registration<{ N }> {
108162
if inner.used == N {
109163
return Err(Error::EINVAL);
110164
}
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-
}
165+
166+
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
167+
// registration.
168+
let fops = unsafe { file_operations::FileOperationsVtable::<Self, T>::build() };
169+
let mut cdev = Cdev::alloc(fops, &this.this_module)?;
170+
cdev.add(inner.dev + inner.used as bindings::dev_t, 1)?;
171+
inner.cdevs[inner.used].replace(cdev);
127172
inner.used += 1;
128173
Ok(())
129174
}
@@ -149,12 +194,9 @@ unsafe impl<const N: usize> Sync for Registration<{ N }> {}
149194
impl<const N: usize> Drop for Registration<{ N }> {
150195
fn drop(&mut self) {
151196
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`.
197+
// SAFETY: TODO needs explanation why unregister_chrdev_region
198+
// is safe here.
154199
unsafe {
155-
for i in 0..inner.used {
156-
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
157-
}
158200
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
159201
}
160202
}

0 commit comments

Comments
 (0)