Skip to content

Commit 56c621e

Browse files
d-e-s-odanielocfb
authored andcommitted
Generate BPF object files on-demand
So far we have compiled BPF object files used for testing purposes and checked them in. That is a security risk: random people on the Internet can create pull requests that include unverified .bpf.o files, which will subsequently be loaded when running tests. With this change we get rid of this nastiness in favor of compiling these object files locally when testing. Aside from the aforementioned security enhancements, this will mean never having to manually remember that some rebuild needs to happen after one of the .bpf.c files was adjusted -- it will happen automatically. Signed-off-by: Daniel Müller <[email protected]>
1 parent df6089e commit 56c621e

21 files changed

+250
-61
lines changed

.github/workflows/test.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,11 @@ jobs:
209209
with:
210210
components: rustfmt
211211
- uses: Swatinem/rust-cache@v2
212-
- run: cargo clippy --locked --no-deps --all-targets --tests -- -D warnings -D clippy::absolute_paths
212+
- run: |
213+
# We want the old resolver here as it has more suitable feature
214+
# unification logic for this invocation.
215+
sed -i 's@resolver = "2"@resolver = "1"@' Cargo.toml
216+
cargo clippy --locked --no-deps --all-targets --tests --features=dont-generate-test-files -- -D warnings -D clippy::absolute_paths
213217
214218
rustfmt:
215219
name: Check code formatting

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ target
55
/examples/*/src/bpf/mod.rs
66
/examples/*/src/bpf/*.skel.rs
77

8+
/libbpf-rs/tests/bin/*.o
9+
810
# These are backup files generated by rustfmt
911
**/*.rs.bk
1012

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libbpf-rs/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,28 @@ static = ["libbpf-sys/static"]
2323
# Use vendored versions of all required libraries.
2424
vendored = ["libbpf-sys/vendored"]
2525

26+
# Below here are dev-mostly features that should not be needed by
27+
# regular users.
28+
29+
# Enable this feature to opt in to the generation of unit test files.
30+
# Having these test files created is necessary for running tests.
31+
generate-test-files = ["libbpf-sys/vendored-libbpf", "dep:tempfile"]
32+
# Disable generation of test files. This feature takes preference over
33+
# `generate-test-files`.
34+
dont-generate-test-files = []
35+
2636
[dependencies]
2737
bitflags = "2.0"
2838
libbpf-sys = { version = "1.4.1", default-features = false }
2939
libc = "0.2"
3040
vsprintf = "2.0"
3141

42+
[build-dependencies]
43+
libbpf-sys = { version = "1.4.1", default-features = false, optional = true }
44+
tempfile = { version = "3.3", optional = true }
45+
3246
[dev-dependencies]
47+
libbpf-rs = {path = ".", features = ["generate-test-files"]}
3348
log = "0.4.4"
3449
memmem = "0.1.1"
3550
plain = "0.2.3"

libbpf-rs/build.rs

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#![allow(clippy::let_unit_value)]
2+
3+
use std::env;
4+
use std::env::consts::ARCH;
5+
use std::ffi::OsStr;
6+
use std::fs::read_dir;
7+
use std::io::Error;
8+
use std::io::ErrorKind;
9+
use std::io::Result;
10+
use std::ops::Deref as _;
11+
use std::path::Path;
12+
use std::process::Command;
13+
use std::process::Stdio;
14+
15+
16+
/// Format a command with the given list of arguments as a string.
17+
fn format_command<C, A, S>(command: C, args: A) -> String
18+
where
19+
C: AsRef<OsStr>,
20+
A: IntoIterator<Item = S>,
21+
S: AsRef<OsStr>,
22+
{
23+
args.into_iter().fold(
24+
command.as_ref().to_string_lossy().into_owned(),
25+
|mut cmd, arg| {
26+
cmd += " ";
27+
cmd += arg.as_ref().to_string_lossy().deref();
28+
cmd
29+
},
30+
)
31+
}
32+
33+
/// Run a command with the provided arguments.
34+
fn run<C, A, S>(command: C, args: A) -> Result<()>
35+
where
36+
C: AsRef<OsStr>,
37+
A: IntoIterator<Item = S> + Clone,
38+
S: AsRef<OsStr>,
39+
{
40+
let instance = Command::new(command.as_ref())
41+
.stdin(Stdio::null())
42+
.stdout(Stdio::null())
43+
.env_clear()
44+
.envs(env::vars().filter(|(k, _)| k == "PATH"))
45+
.args(args.clone())
46+
.output()
47+
.map_err(|err| {
48+
Error::new(
49+
ErrorKind::Other,
50+
format!(
51+
"failed to run `{}`: {err}",
52+
format_command(command.as_ref(), args.clone())
53+
),
54+
)
55+
})?;
56+
57+
if !instance.status.success() {
58+
let code = if let Some(code) = instance.status.code() {
59+
format!(" ({code})")
60+
} else {
61+
" (terminated by signal)".to_string()
62+
};
63+
64+
let stderr = String::from_utf8_lossy(&instance.stderr);
65+
let stderr = stderr.trim_end();
66+
let stderr = if !stderr.is_empty() {
67+
format!(": {stderr}")
68+
} else {
69+
String::new()
70+
};
71+
72+
Err(Error::new(
73+
ErrorKind::Other,
74+
format!(
75+
"`{}` reported non-zero exit-status{code}{stderr}",
76+
format_command(command, args)
77+
),
78+
))
79+
} else {
80+
Ok(())
81+
}
82+
}
83+
84+
fn adjust_mtime(path: &Path) -> Result<()> {
85+
// Note that `OUT_DIR` is only present at runtime.
86+
let out_dir = env::var("OUT_DIR").unwrap();
87+
// The $OUT_DIR/output file is (in current versions of Cargo [as of
88+
// 1.69]) the file containing the reference time stamp that Cargo
89+
// checks to determine whether something is considered outdated and
90+
// in need to be rebuild. It's an implementation detail, yes, but we
91+
// don't rely on it for anything essential.
92+
let output = Path::new(&out_dir)
93+
.parent()
94+
.ok_or_else(|| Error::new(ErrorKind::Other, "OUT_DIR has no parent"))?
95+
.join("output");
96+
97+
if !output.exists() {
98+
// The file may not exist for legitimate reasons, e.g., when we
99+
// build for the very first time. If there is not reference there
100+
// is nothing for us to do, so just bail.
101+
return Ok(())
102+
}
103+
104+
let () = run(
105+
"touch",
106+
[
107+
"-m".as_ref(),
108+
"--reference".as_ref(),
109+
output.as_os_str(),
110+
path.as_os_str(),
111+
],
112+
)?;
113+
Ok(())
114+
}
115+
116+
/// Compile `src` into `dst` using the provided compiler.
117+
fn compile(compiler: &str, src: &Path, dst: &Path, options: &[&str]) {
118+
let dst = src.with_file_name(dst);
119+
println!("cargo:rerun-if-changed={}", src.display());
120+
println!("cargo:rerun-if-changed={}", dst.display());
121+
122+
let () = run(
123+
compiler,
124+
options
125+
.iter()
126+
.map(OsStr::new)
127+
.chain([src.as_os_str(), "-o".as_ref(), dst.as_os_str()]),
128+
)
129+
.unwrap_or_else(|err| panic!("failed to run `{compiler}`: {err}"));
130+
131+
let () = adjust_mtime(&dst).unwrap();
132+
}
133+
134+
/// Extract vendored libbpf header files into a directory.
135+
#[cfg(feature = "generate-test-files")]
136+
fn extract_libbpf_headers(target_dir: &Path) {
137+
use std::fs;
138+
use std::fs::OpenOptions;
139+
use std::io::Write;
140+
141+
let dir = target_dir.join("bpf");
142+
let () = fs::create_dir_all(&dir).unwrap();
143+
for (filename, contents) in libbpf_sys::API_HEADERS.iter() {
144+
let path = dir.as_path().join(filename);
145+
let mut file = OpenOptions::new()
146+
.write(true)
147+
.create(true)
148+
.truncate(true)
149+
.open(path)
150+
.unwrap();
151+
file.write_all(contents.as_bytes()).unwrap();
152+
}
153+
}
154+
155+
#[cfg(feature = "generate-test-files")]
156+
fn with_bpf_headers<F>(f: F)
157+
where
158+
F: FnOnce(&Path),
159+
{
160+
use tempfile::tempdir;
161+
162+
let header_parent_dir = tempdir().unwrap();
163+
let () = extract_libbpf_headers(header_parent_dir.path());
164+
let () = f(header_parent_dir.path());
165+
}
166+
167+
#[cfg(not(feature = "generate-test-files"))]
168+
fn with_bpf_headers<F>(_f: F)
169+
where
170+
F: FnOnce(&Path),
171+
{
172+
unimplemented!()
173+
}
174+
175+
/// Prepare the various test files.
176+
fn prepare_test_files(crate_root: &Path) {
177+
let bin_dir = crate_root.join("tests").join("bin");
178+
let src_dir = bin_dir.join("src");
179+
let include = crate_root.join("../vmlinux/include").join(ARCH);
180+
181+
with_bpf_headers(|bpf_hdr_dir| {
182+
for result in read_dir(&src_dir).unwrap() {
183+
let entry = result.unwrap();
184+
let src = entry.file_name();
185+
let obj = Path::new(&src).with_extension("o");
186+
let src = src_dir.join(&src);
187+
let dst = bin_dir.join(obj);
188+
let arch = option_env!("CARGO_CFG_TARGET_ARCH").unwrap_or(ARCH);
189+
let arch = match arch {
190+
"x86_64" => "x86",
191+
"aarch64" => "arm64",
192+
"powerpc64" => "powerpc",
193+
"s390x" => "s390",
194+
x => x,
195+
};
196+
197+
compile(
198+
"clang",
199+
&src,
200+
&dst,
201+
&[
202+
"-g",
203+
"-O2",
204+
"-target",
205+
"bpf",
206+
"-c",
207+
"-I",
208+
include.to_str().unwrap(),
209+
"-I",
210+
&format!("{}", bpf_hdr_dir.display()),
211+
"-D",
212+
&format!("__TARGET_ARCH_{arch}"),
213+
],
214+
);
215+
}
216+
})
217+
}
218+
219+
fn main() {
220+
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
221+
222+
if cfg!(feature = "generate-test-files") && !cfg!(feature = "dont-generate-test-files") {
223+
prepare_test_files(crate_dir.as_ref());
224+
}
225+
}

libbpf-rs/tests/bin/ksyscall.bpf.o

-5.93 KB
Binary file not shown.
-3.1 KB
Binary file not shown.

libbpf-rs/tests/bin/mapiter.bpf.o

-868 KB
Binary file not shown.

libbpf-rs/tests/bin/percpu_map.bpf.o

-2.56 KB
Binary file not shown.

libbpf-rs/tests/bin/ringbuf.bpf.o

-5.2 KB
Binary file not shown.

libbpf-rs/tests/bin/run_prog.bpf.o

-7.53 KB
Binary file not shown.

libbpf-rs/tests/bin/runqslower.bpf.o

-899 KB
Binary file not shown.

libbpf-rs/tests/bin/src/runqslower.h

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#ifndef __RUNQSLOWER_H
33
#define __RUNQSLOWER_H
44

5+
#include "vmlinux.h"
6+
57
#define TASK_COMM_LEN 16
68

79
struct event {

libbpf-rs/tests/bin/taskiter.bpf.o

-889 KB
Binary file not shown.

libbpf-rs/tests/bin/tc-unit.bpf.o

-6.59 KB
Binary file not shown.

libbpf-rs/tests/bin/tracepoint.bpf.o

-10.8 KB
Binary file not shown.

libbpf-rs/tests/bin/uprobe.bpf.o

-21 KB
Binary file not shown.

libbpf-rs/tests/bin/usdt.bpf.o

-21 KB
Binary file not shown.
-7.14 KB
Binary file not shown.

libbpf-rs/tests/bin/xdp.bpf.o

-3.22 KB
Binary file not shown.

libbpf-rs/tests/bpf_object_regen.sh

-60
This file was deleted.

0 commit comments

Comments
 (0)