Skip to content

Commit fc2b177

Browse files
authored
Merge pull request #207 from TheSven73/rust-for-linux-cdev-uaf
RFC: rust: chrdev: fix use-after-free on module unload
2 parents cbee7ba + 2bcc169 commit fc2b177

File tree

2 files changed

+78
-24
lines changed

2 files changed

+78
-24
lines changed

.github/workflows/ci.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ jobs:
252252
| sed s:$'\r'$:: \
253253
| tee qemu-stdout.log
254254
255+
# The kernel should not be generating any warnings
256+
- run: |
257+
! grep '] WARNING:' qemu-stdout.log
258+
255259
# Check
256260
- run: |
257261
grep '] rust_minimal: Rust minimal sample (init)$' qemu-stdout.log

rust/kernel/chrdev.rs

+74-24
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,67 @@ use crate::error::{Error, Result};
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, non-null and has static lifetime.
28+
/// - [`(*self.0).owner`] is valid and, if non-null, has module 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+
) -> Result<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:
48+
// - [`self.0`] is valid and non-null.
49+
// - [`(*self.0).ops`] is valid, non-null and has static lifetime,
50+
// because it was coerced from a reference with static lifetime.
51+
// - [`(*self.0).owner`] is valid and, if non-null, has module lifetime,
52+
// guaranteed by the [`ThisModule`] invariant.
53+
Ok(Self(cdev))
54+
}
55+
56+
fn add(&mut self, dev: bindings::dev_t, count: c_types::c_uint) -> Result {
57+
// SAFETY: according to the type invariants:
58+
// - [`self.0`] can be safely passed to [`bindings::cdev_add`].
59+
// - [`(*self.0).ops`] will live at least as long as [`self.0`].
60+
// - [`(*self.0).owner`] will live at least as long as the
61+
// module, which is an implicit requirement.
62+
let rc = unsafe { bindings::cdev_add(self.0, dev, count) };
63+
if rc != 0 {
64+
return Err(Error::from_kernel_errno(rc));
65+
}
66+
Ok(())
67+
}
68+
}
69+
70+
impl Drop for Cdev {
71+
fn drop(&mut self) {
72+
// SAFETY: [`self.0`] is valid and non-null by the type invariants.
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<Cdev>; N],
2783
_pin: PhantomPinned,
2884
}
2985

@@ -96,10 +152,11 @@ impl<const N: usize> Registration<{ N }> {
96152
if res != 0 {
97153
return Err(Error::from_kernel_errno(res));
98154
}
155+
const NONE: Option<Cdev> = None;
99156
this.inner = Some(RegistrationInner {
100157
dev,
101158
used: 0,
102-
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
159+
cdevs: [NONE; N],
103160
_pin: PhantomPinned,
104161
});
105162
}
@@ -108,22 +165,13 @@ impl<const N: usize> Registration<{ N }> {
108165
if inner.used == N {
109166
return Err(Error::EINVAL);
110167
}
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-
}
168+
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+
let mut cdev = Cdev::alloc(fops, &this.this_module)?;
173+
cdev.add(inner.dev + inner.used as bindings::dev_t, 1)?;
174+
inner.cdevs[inner.used].replace(cdev);
127175
inner.used += 1;
128176
Ok(())
129177
}
@@ -149,12 +197,14 @@ 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+
// Replicate kernel C behaviour: drop [`Cdev`]s before calling
201+
// [`bindings::unregister_chrdev_region`].
202+
for i in 0..inner.used {
203+
inner.cdevs[i].take();
204+
}
205+
// SAFETY: [`self.inner`] is Some, so [`inner.dev`] was previously
206+
// created using [`bindings::alloc_chrdev_region`].
154207
unsafe {
155-
for i in 0..inner.used {
156-
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
157-
}
158208
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
159209
}
160210
}

0 commit comments

Comments
 (0)