Skip to content

Commit b4be162

Browse files
committed
libbpf-cargo: Wrap generated "unsafe" types in MaybeUninit
Certain Rust types that we use in our generated skeleton are not valid for all bit patterns. Bools, for example, are defined to be one byte in size, but their representation allows only for 0 and 1 values to be used in said byte -- everything else is undefined behavior. Enums have similar problems, in that not the entire space of the backing type is necessarily used by variants. The result is an unsoundness issue in our generated skeletons, because C code could accidentally set an enum to an invalid value and Rust code would exhibit undefined behavior. This change fixes this problem by wrapping the corresponding attributes in MaybeUninit. In so doing users have to be explicit when accessing them and make sure that the current representation is indeed valid. Closes: #589 Signed-off-by: Daniel Müller <[email protected]>
1 parent 3f3b872 commit b4be162

File tree

4 files changed

+63
-9
lines changed

4 files changed

+63
-9
lines changed

examples/capable/src/main.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,16 @@ fn main() -> Result<()> {
187187
let mut open_skel = skel_builder.open()?;
188188
//Pass configuration to BPF
189189
open_skel.rodata_mut().tool_config.tgid = opts.pid; //tgid in kernel is pid in userland
190-
open_skel.rodata_mut().tool_config.verbose = opts.verbose;
191-
open_skel.rodata_mut().tool_config.unique_type = opts.unique_type;
190+
open_skel
191+
.rodata_mut()
192+
.tool_config
193+
.verbose
194+
.write(opts.verbose);
195+
open_skel
196+
.rodata_mut()
197+
.tool_config
198+
.unique_type
199+
.write(opts.unique_type);
192200

193201
let mut skel = open_skel.load()?;
194202
skel.attach()?;

libbpf-cargo/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Unreleased
55
- Adjusted `SkeletonBuilder::clang_args` to accept an iterator of
66
arguments instead of a string
77
- Added `--clang-args` argument to `make` and `build` sub-commands
8+
- Fixed potential unsoundness issues in generated skeletons by wrapping "unsafe"
9+
type in `MaybeUninit`
810
- Updated `libbpf-sys` dependency to `1.3.0`
911
- Bumped minimum Rust version to `1.70`
1012

libbpf-cargo/src/gen/btf.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ use libbpf_rs::ReferencesType;
2424

2525
const ANON_PREFIX: &str = "__anon_";
2626

27+
/// Check whether the provided type is "unsafe" to use.
28+
///
29+
/// A type is considered unsafe by this function if it is not valid for
30+
/// any bit pattern.
31+
fn is_unsafe(ty: BtfType<'_>) -> bool {
32+
let ty = ty.skip_mods_and_typedefs();
33+
34+
btf_type_match!(match ty {
35+
BtfKind::Int(t) => matches!(t.encoding, types::IntEncoding::Bool),
36+
BtfKind::Enum | BtfKind::Enum64 => true,
37+
_ => false,
38+
})
39+
}
40+
2741
pub struct GenBtf<'s> {
2842
btf: Btf<'s>,
2943
// We use refcell here because the design of this type unfortunately causes a lot of borrowing
@@ -359,17 +373,23 @@ impl<'s> GenBtf<'s> {
359373
}
360374

361375
// Rust does not implement `Default` for pointers, no matter if
362-
// the pointee implements it.
376+
// the pointee implements it, and it also doesn't do it for
377+
// `MaybeUninit` constructs, which we use for "unsafe" types.
363378
if self
364379
.type_by_id::<types::Ptr<'_>>(field_ty.type_id())
365380
.is_some()
381+
|| is_unsafe(field_ty)
366382
{
367383
gen_impl_default = true
368384
}
369385
}
370386

371387
match self.type_default(field_ty) {
372-
Ok(def) => {
388+
Ok(mut def) => {
389+
if is_unsafe(field_ty) {
390+
def = format!("std::mem::MaybeUninit::new({def})")
391+
}
392+
373393
impl_default.push(format!(
374394
r#" {field_name}: {field_ty_str}"#,
375395
field_name = if let Some(name) = member.name {
@@ -394,7 +414,13 @@ impl<'s> GenBtf<'s> {
394414
let field_name = if let Some(name) = member.name {
395415
name.to_string_lossy()
396416
} else {
397-
field_ty_str.as_str().into()
417+
Cow::Borrowed(field_ty_str.as_str())
418+
};
419+
420+
let field_ty_str = if is_unsafe(field_ty) {
421+
Cow::Owned(format!("std::mem::MaybeUninit<{field_ty_str}>"))
422+
} else {
423+
Cow::Borrowed(field_ty_str.as_str())
398424
};
399425

400426
agg_content.push(format!(r#" pub {field_name}: {field_ty_str},"#));

libbpf-cargo/src/test.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,10 +2375,17 @@ struct Foo foo;
23752375
"#;
23762376

23772377
let expected_output = r#"
2378-
#[derive(Debug, Default, Copy, Clone)]
2378+
#[derive(Debug, Copy, Clone)]
23792379
#[repr(C)]
23802380
pub struct Foo {
2381-
pub test: __anon_1,
2381+
pub test: std::mem::MaybeUninit<__anon_1>,
2382+
}
2383+
impl Default for Foo {
2384+
fn default() -> Self {
2385+
Foo {
2386+
test: std::mem::MaybeUninit::new(__anon_1::default()),
2387+
}
2388+
}
23822389
}
23832390
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
23842391
#[repr(u32)]
@@ -2414,15 +2421,26 @@ struct Foo foo;
24142421
"#;
24152422

24162423
let expected_output = r#"
2417-
#[derive(Debug, Default, Copy, Clone)]
2424+
#[derive(Debug, Copy, Clone)]
24182425
#[repr(C)]
24192426
pub struct Foo {
24202427
pub a: i32,
24212428
pub b: u16,
24222429
pub c: i16,
2423-
pub d: bool,
2430+
pub d: std::mem::MaybeUninit<bool>,
24242431
pub e: i8,
24252432
}
2433+
impl Default for Foo {
2434+
fn default() -> Self {
2435+
Foo {
2436+
a: i32::default(),
2437+
b: u16::default(),
2438+
c: i16::default(),
2439+
d: std::mem::MaybeUninit::new(bool::default()),
2440+
e: i8::default(),
2441+
}
2442+
}
2443+
}
24262444
"#;
24272445

24282446
let mmap = build_btf_mmap(prog_text);

0 commit comments

Comments
 (0)