Skip to content

Commit d830cf5

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 d830cf5

File tree

2 files changed

+73
-23
lines changed

2 files changed

+73
-23
lines changed

rust/kernel/chrdev.rs

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,66 @@ 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) -> KernelResult<()> {
43+
// SAFETY: call an unsafe function
44+
let rc = unsafe { bindings::cdev_add(self.0, dev, pos) };
45+
if rc != 0 {
46+
return Err(Error::from_kernel_errno(rc));
47+
}
48+
Ok(())
49+
}
50+
51+
fn set_owner(&mut self, module: &crate::ThisModule) {
52+
// SAFETY: dereference of a raw pointer
53+
unsafe {
54+
(*self.0).owner = module.0;
55+
}
56+
}
57+
}
58+
59+
fn new_none_array<T, const N: usize>() -> [Option<T>; N] {
60+
// SAFETY: manipulate MaybeUninit memory
61+
unsafe {
62+
let mut arr: [MaybeUninit<Option<T>>; N] = MaybeUninit::uninit_array();
63+
for elem in &mut arr {
64+
elem.as_mut_ptr().write(None);
65+
}
66+
MaybeUninit::array_assume_init(arr)
67+
}
68+
}
69+
70+
impl Drop for SafeCdev {
71+
fn drop(&mut self) {
72+
// SAFETY: call an unsafe function
73+
unsafe {
74+
bindings::cdev_del(self.0);
75+
}
76+
}
77+
}
78+
2379
struct RegistrationInner<const N: usize> {
2480
dev: bindings::dev_t,
2581
used: usize,
26-
cdevs: [MaybeUninit<bindings::cdev>; N],
82+
cdevs: [Option<SafeCdev>; N],
2783
_pin: PhantomPinned,
2884
}
2985

@@ -99,7 +155,7 @@ impl<const N: usize> Registration<{ N }> {
99155
this.inner = Some(RegistrationInner {
100156
dev,
101157
used: 0,
102-
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
158+
cdevs: new_none_array(),
103159
_pin: PhantomPinned,
104160
});
105161
}
@@ -108,22 +164,15 @@ impl<const N: usize> Registration<{ N }> {
108164
if inner.used == N {
109165
return Err(Error::EINVAL);
110166
}
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-
}
167+
168+
let mut cdev = SafeCdev::alloc()?;
169+
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
170+
// registration.
171+
let fops = unsafe { file_operations::FileOperationsVtable::<Self, T>::build() };
172+
cdev.init(fops);
173+
cdev.set_owner(&this.this_module);
174+
cdev.add(inner.dev + inner.used as bindings::dev_t, 1)?;
175+
inner.cdevs[inner.used].replace(cdev);
127176
inner.used += 1;
128177
Ok(())
129178
}
@@ -149,12 +198,11 @@ unsafe impl<const N: usize> Sync for Registration<{ N }> {}
149198
impl<const N: usize> Drop for Registration<{ N }> {
150199
fn drop(&mut self) {
151200
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`.
201+
for i in 0..inner.used {
202+
inner.cdevs[i].take();
203+
}
204+
// SAFETY: Calling unsafe function
154205
unsafe {
155-
for i in 0..inner.used {
156-
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
157-
}
158206
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
159207
}
160208
}

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)