Skip to content

Commit 01945fa

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 01945fa

File tree

1 file changed

+10
-4
lines changed

1 file changed

+10
-4
lines changed

rust/kernel/chrdev.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::types::CStr;
2323
struct RegistrationInner<const N: usize> {
2424
dev: bindings::dev_t,
2525
used: usize,
26-
cdevs: [MaybeUninit<bindings::cdev>; N],
26+
cdevs: [MaybeUninit<*mut bindings::cdev>; N],
2727
_pin: PhantomPinned,
2828
}
2929

@@ -99,7 +99,7 @@ impl<const N: usize> Registration<{ N }> {
9999
this.inner = Some(RegistrationInner {
100100
dev,
101101
used: 0,
102-
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
102+
cdevs: [MaybeUninit::uninit(); N],
103103
_pin: PhantomPinned,
104104
});
105105
}
@@ -108,10 +108,14 @@ impl<const N: usize> Registration<{ N }> {
108108
if inner.used == N {
109109
return Err(Error::EINVAL);
110110
}
111-
let cdev = inner.cdevs[inner.used].as_mut_ptr();
111+
112112
// SAFETY: Calling unsafe functions and manipulating `MaybeUninit`
113113
// pointer.
114114
unsafe {
115+
let cdev = bindings::cdev_alloc();
116+
if cdev.is_null() {
117+
return Err(Error::ENOMEM);
118+
}
115119
bindings::cdev_init(
116120
cdev,
117121
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
@@ -121,8 +125,10 @@ impl<const N: usize> Registration<{ N }> {
121125
(*cdev).owner = this.this_module.0;
122126
let rc = bindings::cdev_add(cdev, inner.dev + inner.used as bindings::dev_t, 1);
123127
if rc != 0 {
128+
bindings::cdev_del(cdev);
124129
return Err(Error::from_kernel_errno(rc));
125130
}
131+
core::ptr::write(inner.cdevs[inner.used].as_mut_ptr(), cdev);
126132
}
127133
inner.used += 1;
128134
Ok(())
@@ -153,7 +159,7 @@ impl<const N: usize> Drop for Registration<{ N }> {
153159
// `inner.cdevs` are initialized in `Registration::register`.
154160
unsafe {
155161
for i in 0..inner.used {
156-
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
162+
bindings::cdev_del(*inner.cdevs[i].as_mut_ptr());
157163
}
158164
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
159165
}

0 commit comments

Comments
 (0)