Skip to content

Commit e3e46dc

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 e3e46dc

File tree

2 files changed

+71
-22
lines changed

2 files changed

+71
-22
lines changed

rust/kernel/chrdev.rs

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,62 @@ use crate::error::{Error, KernelResult};
2020
use crate::file_operations;
2121
use crate::types::CStr;
2222

23+
struct SafeCdev(*mut bindings::cdev);
24+
25+
impl SafeCdev {
26+
fn alloc() -> KernelResult<Self> {
27+
// SAFETY: call an unsafe function
28+
let cdev = unsafe { bindings::cdev_alloc() };
29+
if cdev.is_null() {
30+
return Err(Error::ENOMEM);
31+
}
32+
Ok(Self(cdev))
33+
}
34+
35+
fn init(&mut self, fops: *const bindings::file_operations) {
36+
// SAFETY: call an unsafe function
37+
unsafe {
38+
bindings::cdev_init(self.0, fops);
39+
}
40+
}
41+
42+
fn add(&mut self, dev: bindings::dev_t, pos: c_types::c_uint) -> c_types::c_int {
43+
// SAFETY: call an unsafe function
44+
unsafe { bindings::cdev_add(self.0, dev, pos) }
45+
}
46+
47+
fn set_owner(&mut self, module: &crate::ThisModule) {
48+
// SAFETY: dereference of a raw pointer
49+
unsafe {
50+
(*self.0).owner = module.0;
51+
}
52+
}
53+
}
54+
55+
fn new_none_array<T, const N: usize>() -> [Option<T>; N] {
56+
// SAFETY: manipulate MaybeUninit memory
57+
unsafe {
58+
let mut arr: [MaybeUninit<Option<T>>; N] = MaybeUninit::uninit_array();
59+
for elem in &mut arr {
60+
elem.as_mut_ptr().write(None);
61+
}
62+
MaybeUninit::array_assume_init(arr)
63+
}
64+
}
65+
66+
impl Drop for SafeCdev {
67+
fn drop(&mut self) {
68+
// SAFETY: call an unsafe function
69+
unsafe {
70+
bindings::cdev_del(self.0);
71+
}
72+
}
73+
}
74+
2375
struct RegistrationInner<const N: usize> {
2476
dev: bindings::dev_t,
2577
used: usize,
26-
cdevs: [MaybeUninit<bindings::cdev>; N],
78+
cdevs: [Option<SafeCdev>; N],
2779
_pin: PhantomPinned,
2880
}
2981

@@ -99,7 +151,7 @@ impl<const N: usize> Registration<{ N }> {
99151
this.inner = Some(RegistrationInner {
100152
dev,
101153
used: 0,
102-
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
154+
cdevs: new_none_array(),
103155
_pin: PhantomPinned,
104156
});
105157
}
@@ -108,22 +160,18 @@ impl<const N: usize> Registration<{ N }> {
108160
if inner.used == N {
109161
return Err(Error::EINVAL);
110162
}
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-
}
163+
164+
let mut cdev = SafeCdev::alloc()?;
165+
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
166+
// registration.
167+
let fops = unsafe { file_operations::FileOperationsVtable::<Self, T>::build() };
168+
cdev.init(fops);
169+
cdev.set_owner(&this.this_module);
170+
let rc = cdev.add(inner.dev + inner.used as bindings::dev_t, 1);
171+
if rc != 0 {
172+
return Err(Error::from_kernel_errno(rc));
126173
}
174+
inner.cdevs[inner.used].replace(cdev);
127175
inner.used += 1;
128176
Ok(())
129177
}
@@ -149,12 +197,11 @@ unsafe impl<const N: usize> Sync for Registration<{ N }> {}
149197
impl<const N: usize> Drop for Registration<{ N }> {
150198
fn drop(&mut self) {
151199
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`.
200+
for i in 0..inner.used {
201+
inner.cdevs[i].take();
202+
}
203+
// SAFETY: Calling unsafe function
154204
unsafe {
155-
for i in 0..inner.used {
156-
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
157-
}
158205
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
159206
}
160207
}

rust/kernel/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
const_fn,
1919
const_mut_refs,
2020
const_panic,
21+
maybe_uninit_array_assume_init,
22+
maybe_uninit_uninit_array,
2123
try_reserve
2224
)]
2325
#![deny(clippy::complexity)]

0 commit comments

Comments
 (0)