qmonnet / rbpf Goto Github PK
View Code? Open in Web Editor NEWRust virtual machine and JIT compiler for eBPF programs
License: Apache License 2.0
Rust virtual machine and JIT compiler for eBPF programs
License: Apache License 2.0
eBPF in Linux kernel just got extended to use the 0x06
class of instructions for 32-bit jumps. See the merge commit and its parents. This should land in kernel 5.1.
To keep rbpf compatibility with Linux eBPF as close as possible, it would be nice to have support for those instructions for the interpreter as well as for the JIT in the future.
Input
in.zip
Code
fn main() {
// let filepath = input file in the zip
let data = std::fs::read(filepath).unwrap();
if let Ok(vm) = rbpf::EbpfVmNoData::new(Some(&data)) {
vm.execute_program();
}
}
Output
thread 'main' panicked at 'attempt to divide by zero', /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:461:33
stack backtrace:
0: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
1: core::fmt::write
2: std::io::Write::write_fmt
3: std::panicking::default_hook::{{closure}}
4: std::panicking::default_hook
5: std::panicking::rust_panic_with_hook
6: rust_begin_unwind
7: core::panicking::panic_fmt
8: core::panicking::panic
9: rbpf::EbpfVmMbuff::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:461
10: rbpf::EbpfVmRaw::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1257
11: rbpf::EbpfVmNoData::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1540
Expect
properly return error instead of panic
According to the kernel eBPF standard, Shift operations use a mask of 0x3F (63) for 64-bit operations and 0x1F (31) for 32-bit operations.
In both ubpf and kernel eBPF implementation, it masks the operation of LSH/RSH:
https://github.com/iovisor/ubpf/blob/4b9a1cc747cbb9a9aad025a68aaedff7211f7fc2/vm/ubpf_vm.c#L495-L506
https://github.com/torvalds/linux/blob/610a9b8f49fbcf1100716370d3b5f6f884a2835a/kernel/bpf/core.c#L1712-L1724
In the rbpf interpreter, it lacks the masking operation.
Lines 225 to 228 in 4812c52
Moreover, in LSH64/RSH64
, it doesn't check the range of src value, which could result in the 'attempt to shift left with overflow'
panic as the following PoC program shows.
Lines 272 to 275 in 4812c52
This program would trigger the 'attempt to shift left with overflow'
panic in interpreter
mov64 r8, 0x054545ff
lsh64 r8, r8
exit
Currently rbpf panics for any error. Instead, where possible fail gracefully and return an error to the user.
This also has implications for the verification function. Its current signature returns a bool but rbpf ignores it and expects the verification function to panic. Part of addressing this issue is to respect the return value and potentially pass a more specific failure code up to the user.
There exists several integer overflow in the mem_check
check. The addition of 'addr' and 'len' variables may result in overflow towards a lower address, circumventing the checks on pointer addresses.
Lines 15 to 23 in 4812c52
Take addr + len as u64 <= mbuff.as_ptr() as u64 + mbuff.len() as u64
for an example, if addr
is the mbuff pointer address and len
is -1
, the final result overflow towards mbuff.ptr() -1
, causing the bad memory access.
The following PoC program will violate the safety check and broke the interpreter.
stdw [r2-0x1], 0x380affff
exit
With overflow-check enabled, we coud get the following panic:
thread '<unnamed>' panicked at src/interpreter.rs:15:41:
attempt to add with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
...
#14 0x10668d704 in core::panicking::panic::h28efa1d8b603254d panicking.rs:144
#15 0x104fe6ed4 in rbpf::interpreter::check_mem::he501d64976cf2caa interpreter.rs:15
#16 0x104fe858c in rbpf::interpreter::execute_program::he7cae8749900d08a interpreter.rs:176
I do not see any particular reason to offer the asm_parser
to the end user. As I see it, all the assembler API holds in the assembler
module. What about making asm_parser
private in src/lib.rs
? @rlane, any thought?
Note that as a consequence, we would have to move the tests from the separate file to a test
module at the end of file src/asm_parser.rs
. (I don't think the function/stucts from the module would remain accessible otherwise).
Line 380 in ceb7fa6
We want to tag a new version and update the crate available for download on crates.io.
ETA: end of this month.
There have been quite some changes since last version, although I'm not sure the current version is stable enough to deserve 1.0.0. Therefore I consider bumping version number to 0.1.0.
Things we want to fix and complete before that:
Interested in doing a pass of cargo fmt
?
See kernel commit 078295fb9af5 and following.
So we declare a type for a verifier function:
pub type Verifier = fn(prog: &[u8]) -> Result<(), Error>;
Would be nice to have the same for the helpers. So that instead of:
pub fn register_helper(&mut self,
key: u32,
function: fn (u64, u64, u64, u64, u64) -> u64)
-> Result<(), Error>
... we'd have something like:
pub fn register_helper(&mut self,
key: u32,
function: Helper) -> Result<(), Error>
(EbpfHelper
or Helper
, I don't really know what sounds best. Maybe just Helper
if people call that with the rbpf::
prefix already?)
From discussion on #32.
This way they are automatically compiled on test and provide a ready-to-use example file for user.
For the bigger example loading an elf it might be a nice idea to include a minimal C file and how to compile it to use with the example. Dependencies for that example need dependencies, which can be added as dev-dependencies
.
If interested I can take care of that.
I run it in router
[Linux RT-AC68U 2.6.36.4brcmarm #1 SMP PREEMPT Fri May 10 22:16:14 CST 2019 armv7l GNU/Linux],
execute_program_jit is alway Segmentation fault
main.rs:
extern crate rbpf;
fn main() {
let prog = &[
0x71, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxh r0, [r1+2]
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
];
// Let's use some data.
let mem = &mut [
0xaa, 0xbb, 0x11, 0xcc, 0xdd
];
println!("next EbpfVmRaw::new");
// This is an eBPF VM for programs reading from a given memory area (it
// directly reads from packet data)
let mut vm = rbpf::EbpfVmRaw::new(prog).unwrap();
println!("next jit_compile");
// This time we JIT-compile the program.
vm.jit_compile().unwrap();
println!("next execute_program_jit");
// Then we execute it. For this kind of VM, a reference to the packet data
// must be passed to the function that executes the program.
unsafe { assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x11); }
println!("finnal");
}
run it in router:
next EbpfVmRaw::new
next jit_compile
next execute_program_jit
Segmentation fault
The disassembler would panic in ld_st_imm_str
when it tries to negate the 0x8000i16 value in
Lines 29 to 33 in 4812c52
The PoC program to reproduce:
disassembler::disassemble(&[98, 1, 0, 128, 0, 0, 31, 145])
The would panic the disassembler:
thread '<unnamed>' panicked at 'attempt to negate with overflow', /rbpf-0.2.0/src/disassembler.rs:33:56
To enhance the robustness of the disassembler, the negation logic of i16 could be restructured in ld_st_imm_str
function.
Input
in.zip
Code
fn main() {
// let filepath = input file in the zip
let data = std::fs::read(filepath).unwrap();
if let Ok(vm) = rbpf::EbpfVmNoData::new(Some(&data)) {
vm.execute_program();
}
}
Output
thread 'main' panicked at 'attempt to calculate the remainder with a divisor of zero', /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:477:33
stack backtrace:
0: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
1: core::fmt::write
2: std::io::Write::write_fmt
3: std::panicking::default_hook::{{closure}}
4: std::panicking::default_hook
5: std::panicking::rust_panic_with_hook
6: rust_begin_unwind
7: core::panicking::panic_fmt
8: core::panicking::panic
9: rbpf::EbpfVmMbuff::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:477
10: rbpf::EbpfVmRaw::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1257
11: rbpf::EbpfVmNoData::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1540
12: rbpffuzzvrf::main
Expect
properly return error instead of panic
Updating dependency combine
from 2.1.1 to 3.6 will require some code rework since there were non-compatible api changes.
Updating the rest of the dependencies to the following did not change any test results:
combine = "2.1.1"
-libc = "0.2.0"
+libc = "0.2"
time = "0.1"
-byteorder = "1.2.1"
+byteorder = "1.2"
[dev-dependencies]
elf = "0.0.10"
-json = "0.11.4"
+json = "0.11"
Segmentation fault when executting the jitted ebpf program. But the same program works well when using intepreter mode.
[package]
name = "rbpf-poc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rbpf = { git = "https://github.com/qmonnet/rbpf" }
fn main() {
let prog = std::fs::read("prime.bpf.bin").unwrap();
let mut vm = rbpf::EbpfVmRaw::new(Some(&prog)).unwrap();
vm.jit_compile().unwrap();
let mem = &mut [0x00];
unsafe { vm.execute_program_jit(mem) }.unwrap();
}
It was zipped, since github does not support uploading *.bin
files.
The ebpf program was compiled from the following C source using clang 14.0.6
, then the .text
segment was extracted using llvm-objcopy
int main() {
long cnt = 0;
for (int i = 1; i < 1e4; i++) {
int ok = 1;
for (int j = 2; j * j <= i && ok; j++) {
if (i % j == 0)
ok = 0;
}
cnt += ok;
}
return cnt;
}
root@mnfe-pve:~/rbpf-poc# cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/rbpf-poc`
Segmentation fault
Program successfully exited with no exception
stable-x86_64-unknown-linux-gnu (default)
rustc 1.71.0 (8ede3aae2 2023-07-12)
root@mnfe-pve:~/bpf-benchmark# uname -a
Linux mnfe-pve 6.2.16-6-pve #1 SMP PREEMPT_DYNAMIC PMX 6.2.16-7 (2023-08-01T11:23Z) x86_64 GNU/Linux
root@mnfe-pve:~/bpf-benchmark# lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 12 (bookworm)
Release: 12
Codename: bookworm
root@mnfe-pve:~/bpf-benchmark#
The README.md
file is somewhat outdated. In particular:
If you stumble on something else, do not hesitate to add elements to update below, so that I do not forget them the day I dive into this!
Working on #6, looking at #10, #9 and #8. I see that we have a lack of high-level stack of bpf
instructions.
Personally, I see it as the following:
struct InsnStack {
head: RefToInsn // points on the first instruction that will be executed
tail: RefToInsn // points on `exit` instuction, unless it is an invalid bpf program
}
impl InsnStack {
pub fn create() -> Self {
//...
}
pub fn disassemble() -> String {
//...
}
//may be a bunch of functions from #6 ... I am ok if we remove `insn_builder` module
pub fn patch(patch: MbuffStructure) {
//...
}
pub fn validate() -> Result<ValidInsnStack, ValidationError> { // <- the same as InsnStack but `tail` always points to `exit` Insn :smile:
//...
}
}
pub fn parse_assemble(raw_bpf_asm: String) -> InsnStack {
//...
}
RefToInsn
can be any type of reference, however, we could not use Box
here.
Having that structure we can provide API to easily manipulate its content.
@qmonnet, thoughts?
PS1: again as in #12 we need to do some prototype here.
PS2: I also don't like that we have the bunch of different structures that in some or other way represent bpf
instruction, but I don't see the solution to this problem right now.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
Custom {
kind: Other,
error: "Error: out of bounds memory store (insn #2), addr 0x2, size 4
mbuff: 0x560d8e8a15b0/0x0,
mem: 0x1/0x0,
stack: 0x560d8f935e70/0x200"
}', examples/rbpf_plugin.rs:45:50
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Please, do not start working on this, someone already asked me by email to start the work on the assembler.
New feature: an assembler similar to the one of uBPF, to build eBPF programs with a syntax similar to:
mov32 r0, 0
mov32 r1, 2
add32 r0, 1
add32 r0, r1
exit
This would make rbpf natively compatible with uBPF test cases (most functional unit tests have been translated into bytecode already, but it would make it more readable to have them in human-readable form, and it would help be compatible when uBPF receives new test cases.
Improvement suggestion: use add64
instead of add
for 64-bits arithmetic operations (but also accept add
only, to remain compatible).
Tests consistently fail with recent versions of the toolchain, with messages such as:
thread 'test_jit_mbuff' panicked at 'misaligned pointer dereference: address must be a multiple of 0x4 but is 0x7f8038001011', src/jit.rs:100:5
Running tests/misc.rs (target/debug/deps/misc-6f56f75d4e6d43c9)
running 20 tests
thread 'test_jit_mbuff' panicked at 'misaligned pointer dereference: address must be a multiple of 0x4 but is 0x7f8038001011', src/jit.rs:100:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'test_jit_mbuff' panicked at 'panic in a function that cannot unwind', library/core/src/panicking.rs:126:5
stack backtrace:
0: 0x562d63bf4351 - std::backtrace_rs::backtrace::libunwind::trace::h28494931c73179b2
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
1: 0x562d63bf4351 - std::backtrace_rs::backtrace::trace_unsynchronized::h9032c52edccf7bd1
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: 0x562d63bf4351 - std::sys_common::backtrace::_print_fmt::hd90562e967f4e4e1
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys_common/backtrace.rs:65:5
3: 0x562d63bf4351 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h113657117676131e
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys_common/backtrace.rs:44:22
4: 0x562d63c1995f - core::fmt::rt::Argument::fmt::hd56cdfa11c364505
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/fmt/rt.rs:138:9
5: 0x562d63c1995f - core::fmt::write::h24c20284e5d6be9e
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/fmt/mod.rs:1094:21
6: 0x562d63bf1b41 - std::io::Write::write_fmt::hbf02c94f0e7342d1
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/io/mod.rs:1712:15
7: 0x562d63bf4165 - std::sys_common::backtrace::_print::he85212e2c716c859
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys_common/backtrace.rs:47:5
8: 0x562d63bf4165 - std::sys_common::backtrace::print::h888aaf3ad10f084e
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys_common/backtrace.rs:34:9
9: 0x562d63bf5cb7 - std::panicking::default_hook::{{closure}}::hba0edb58dc223add
10: 0x562d63bf5aa4 - std::panicking::default_hook::h1555b8bada2010d7
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:288:9
11: 0x562d63b6cdd4 - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::h52e6cd440f597cb6
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/alloc/src/boxed.rs:1999:9
12: 0x562d63b6cdd4 - test::test_main::{{closure}}::hdcda637653172e31
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/test/src/lib.rs:134:21
13: 0x562d63bf62c7 - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::h07438796673f3d04
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/alloc/src/boxed.rs:1999:9
14: 0x562d63bf62c7 - std::panicking::rust_panic_with_hook::h72a06453beb2cbcb
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:695:13
15: 0x562d63bf6001 - std::panicking::begin_panic_handler::{{closure}}::h0281d6cc05cfd2a4
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:580:13
16: 0x562d63bf4796 - std::sys_common::backtrace::__rust_end_short_backtrace::h1c79565770be27d9
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys_common/backtrace.rs:150:18
17: 0x562d63bf5db2 - rust_begin_unwind
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:578:5
18: 0x562d63b32b33 - core::panicking::panic_nounwind_fmt::hdb51e63c7c599e80
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/panicking.rs:96:14
19: 0x562d63b32bd7 - core::panicking::panic_nounwind::hc4511f17cef1f7da
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/panicking.rs:126:5
20: 0x562d63b32d63 - core::panicking::panic_cannot_unwind::hbac7ba3f5f929c6c
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/panicking.rs:188:5
21: 0x562d63b8f4e4 - rbpf::jit::emit4::haff8aacbfffbe8b6
at /home/runner/work/rbpf/rbpf/src/jit.rs:99:1
22: 0x562d63b8ff07 - rbpf::jit::emit_alu64_imm32::hd930f0d06f862872
at /home/runner/work/rbpf/rbpf/src/jit.rs:211:5
23: 0x562d63b911af - rbpf::jit::JitMemory::jit_compile::h0a631e7ea59867f2
at /home/runner/work/rbpf/rbpf/src/jit.rs:544:9
24: 0x562d63b93722 - rbpf::jit::compile::hb7b83a17bcef25db
at /home/runner/work/rbpf/rbpf/src/jit.rs:1014:5
25: 0x562d63b842d4 - rbpf::EbpfVmMbuff::jit_compile::h1c675e69584d7361
at /home/runner/work/rbpf/rbpf/src/lib.rs:643:25
26: 0x562d63b34ed0 - misc::test_jit_mbuff::h8eb4bceb4b166566
at /home/runner/work/rbpf/rbpf/tests/misc.rs:336:9
27: 0x562d63b37b57 - misc::test_jit_mbuff::{{closure}}::h55d6770297b8bea5
at /home/runner/work/rbpf/rbpf/tests/misc.rs:314:21
28: 0x562d63b377e5 - core::ops::function::FnOnce::call_once::hafeaeb2bdb95188a
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/ops/function.rs:250:5
29: 0x562d63b7232f - core::ops::function::FnOnce::call_once::heba63a2808b93cd2
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/ops/function.rs:250:5
30: 0x562d63b7232f - test::__rust_begin_short_backtrace::he9a5c6c59e1b0086
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/test/src/lib.rs:655:18
31: 0x562d63b3e22c - test::run_test::{{closure}}::h1b8c22c7d438b531
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/test/src/lib.rs:646:30
32: 0x562d63b3e22c - core::ops::function::FnOnce::call_once{{vtable.shim}}::h36a9083506ac2a12
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/ops/function.rs:250:5
33: 0x562d63b711f7 - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h11e5ff78080e1397
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/alloc/src/boxed.rs:1985:9
34: 0x562d63b711f7 - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::hfbfbda226f7a7c27
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/panic/unwind_safe.rs:271:9
35: 0x562d63b711f7 - std::panicking::try::do_call::h42ac7b4bd35c5b5d
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:485:40
36: 0x562d63b711f7 - std::panicking::try::hc6ad089a257e422e
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:449:19
37: 0x562d63b711f7 - std::panic::catch_unwind::hdf4784c0c06ac0ba
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panic.rs:140:14
38: 0x562d63b711f7 - test::run_test_in_process::hbe86cacc7510eb8b
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/test/src/lib.rs:678:27
39: 0x562d63b711f7 - test::run_test::run_test_inner::{{closure}}::hbaa8a391d0f22860
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/test/src/lib.rs:572:39
40: 0x562d63b38c28 - test::run_test::run_test_inner::{{closure}}::hb4232323322f922d
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/test/src/lib.rs:599:37
41: 0x562d63b38c28 - std::sys_common::backtrace::__rust_begin_short_backtrace::hca81c15946d6f154
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys_common/backtrace.rs:134:18
42: 0x562d63b3e45b - std::thread::Builder::spawn_unchecked_::{{closure}}::{{closure}}::h97d990ba33daf685
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/thread/mod.rs:529:17
43: 0x562d63b3e45b - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::he0d612b643c049a4
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/panic/unwind_safe.rs:271:9
44: 0x562d63b3e45b - std::panicking::try::do_call::haf308dcb076117ac
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:485:40
45: 0x562d63b3e45b - std::panicking::try::h51fd60d29cf6feee
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panicking.rs:449:19
46: 0x562d63b3e45b - std::panic::catch_unwind::h8e7f9d8c4a32bc2f
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/panic.rs:140:14
47: 0x562d63b3e45b - std::thread::Builder::spawn_unchecked_::{{closure}}::h07454567f5413f25
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/thread/mod.rs:528:30
48: 0x562d63b3e45b - core::ops::function::FnOnce::call_once{{vtable.shim}}::hfb80b7cf501e16ff
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/core/src/ops/function.rs:250:5
49: 0x562d63bfa815 - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h71c821b130855373
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/alloc/src/boxed.rs:1985:9
50: 0x562d63bfa815 - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h1701cec9acb1061c
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/alloc/src/boxed.rs:1985:9
51: 0x562d63bfa815 - std::sys::unix::thread::Thread::new::thread_start::h6dd5ef62dde103f1
at /rustc/2f6bc5d259e7ab25ddfdd33de53b892770218918/library/std/src/sys/unix/thread.rs:108:17
52: 0x7f803c6e9b43 - <unknown>
53: 0x7f803c77ba00 - <unknown>
54: 0x0 - <unknown>
thread caused non-unwinding panic. aborting.
error: test failed, to rerun pass `--test misc`
This seems to be a consequence of rust-lang/rust#98112.
As an easy “fix”, we can disable debug assertions for the tests, but we risk introducing more bugs. Not sure how to fix it otherwise, given that we do support unaligned memory access in rbpf at this time.
From the #6
PS: working on this API I start thinking about this RawBpfCode and VerifiedBpfCode and let myself create needless functionality. And I understood the reason. It is because my code is OOP-like rather then procedural-like as in the other modules. 😄
I start to imagine that we could write chaining code for BPF program in general. For instance:
let bpf_vm = ... // create VM of any type RawBpfCode::new().load(...).push().store(...).push() // or RawBpfCode::parse(...) // or RawBpfCode::from_elf(...) .verify().map(|verified| verified.execute_with(bpf_vm)).err().map(...);and RawBpfCode::parse() can actually use asm_parser functionality to be synched with it.
Taking into account @qmonnet comment:
we already have the “procedural-like” approach that works well, why change it
we can improve procedural
API that instead of panic! return Result
.
Any thoughts?
👍 It'd be nice to eventually have a unified test suite that would run in cranelift, the JIT and the interpreter!
Originally posted by @afonso360 in #86 (comment)
In the implementation of DIV32
, it checks whether the 64-bit source value is zero. However, during the division computation, the source is casted to 32-bit, which could still be zero, e.g., 0xffff000000000000. According to the kernel ebpf standard, we should check the division by zero
by checking the nullness of the 32-bit source value.
If BPF program execution would result in division by zero, the destination register is instead set to zero.
The current implementation of DIV32_IMM
and DIV32_REG
lacks the check to the lower 32 bits of source value.
Lines 217 to 220 in 4812c52
The following PoC program would cause the attempt to calculate the remainder with a divisor of zero
panic in the interpreter:
lddw r1, 0x100000000
div32 r0, r1
exit
Result:
attempt to divide by zero
thread '' panicked at 'attempt to divide by zero', src/interpreter.rs:222:45
stack backtrace:
...
3: rbpf::interpreter::execute_program
at ./src/interpreter.rs:222:45
4: rbpf::EbpfVmMbuff::execute_program
at ./src/lib.rs:300:9
5: rbpf::EbpfVmRaw::execute_program
at ./src/lib.rs:1221:9
The expected result is that the program executed with r0 set to zero, without panic/error.
Check nullness of 32-bit casted source value before division
The disassembler would panic in ld_reg_str
when it tries to negate the 0x8000i16 value in -insn.off
Lines 37 to 42 in 4812c52
The PoC program to reproduce:
disassembler::disassemble(&[113, 1, 0, 128, 0, 0, 31, 145])
This would encounter the following panic:
thread '<unnamed>' panicked at 'attempt to negate with overflow'
Noted that, this is issue is also encountered in st_reg_str
, jmp_imm_str
, jmp_reg_str
functions.
To enhance the robustness of the disassembler, the negation logic of i16 could be restructured in those function.
This issue follows #99 and #100. The previous PR demonstrates the implementation incompliance on the logic shift implementation. However, the arithmetic shift operation still meets this problem, and needs the additional mask offset as well.
The current implementation of ARSH64 is not compliant, and undefined behavior happens if we overflow the number of bits we have when trying to shift.
The following PoC program could trigger this inconsistency:
mov64 r8, 0x054545ff
arsh64 r8, r8
exit
Note that in the kernel interpreter implementation, it masks the SRC
/IMM
as well.
https://github.com/torvalds/linux/blob/610a9b8f49fbcf1100716370d3b5f6f884a2835a/kernel/bpf/core.c#L1794-L1805
Hence we need to mask the shift offset with 63 in the following code. Since we don't perform verifier check on the immediate value, we need to mask the IMM
as well.
Lines 289 to 290 in 7bebff5
I liked the type you declared for a verifier:
pub type Verifier = fn(prog: &[u8]) -> Result<(), Error>;
Do you think it would be worth doing the same for the helpers? So that instead of:
pub fn register_helper(&mut self,
key: u32,
function: fn (u64, u64, u64, u64, u64) -> u64)
-> Result<(), Error>
... we'd have something like:
pub fn register_helper(&mut self,
key: u32,
function: Helper) -> Result<(), Error>
(EbpfHelper or Helper, I don't really know what sounds best. Maybe just Helper if people call that with the rbpf:: prefix already?)
We don't support eBPF atomic instructions yet.
See https://lore.kernel.org/bpf/[email protected]/t/#u for some context.
Originally posted by @qmonnet in #47 (comment)
The rbpf runtime incorrectly rejects programs where the last instruction is not an exit instruction.
See https://github.com/Alan-Jowett/bpf_conformance/blob/main/tests/exit-not-last.data as an example.
This test passes the Linux verifier.
Input
in.zip
Code
fn main() {
// let filepath = input file in the zip
let data = std::fs::read(filepath).unwrap();
if let Ok(vm) = rbpf::EbpfVmNoData::new(Some(&data)) {
vm.execute_program();
}
}
Output
thread 'main' panicked at 'attempt to add with overflow', /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:583:45
stack backtrace:
0: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
1: core::fmt::write
2: std::io::Write::write_fmt
3: std::panicking::default_hook::{{closure}}
4: std::panicking::default_hook
5: std::panicking::rust_panic_with_hook
6: rust_begin_unwind
7: core::panicking::panic_fmt
8: core::panicking::panic
9: rbpf::EbpfVmMbuff::check_mem
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:583
10: rbpf::EbpfVmMbuff::execute_program::{{closure}}
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:301
11: rbpf::EbpfVmMbuff::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:372
12: rbpf::EbpfVmRaw::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1257
13: rbpf::EbpfVmNoData::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1540
Expect
properly return error instead of panic
macOS doesn't have clock_gettime
or CLOCK_MONOTONIC
, so the bpf_time_getns
helper function fails to compile on macOS.
Input
in.zip
Code
fn main() {
// let filepath = input file in the zip
let data = std::fs::read(filepath).unwrap();
if let Ok(vm) = rbpf::EbpfVmNoData::new(Some(&data)) {
vm.execute_program();
}
}
Output
thread 'main' panicked at 'attempt to shift left with overflow', /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:520:37
stack backtrace:
0: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
1: core::fmt::write
2: std::io::Write::write_fmt
3: std::panicking::default_hook::{{closure}}
4: std::panicking::default_hook
5: std::panicking::rust_panic_with_hook
6: rust_begin_unwind
7: core::panicking::panic_fmt
8: core::panicking::panic
9: rbpf::EbpfVmMbuff::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:520
10: rbpf::EbpfVmRaw::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1257
11: rbpf::EbpfVmNoData::execute_program
at /home/xsh/.cargo/registry/src/github.com-1ecc6299db9ec823/rbpf-0.1.0/src/lib.rs:1540
Expect
properly return error instead of panic
Appveyor runs often fails with:
[...]
C:\projects\rbpf>cargo test -vv
Updating crates.io index
Downloading crates ...
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (2 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
warning: spurious network error (1 tries remaining): [35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
error: failed to download from `https://crates.io/api/v1/crates/json/0.11.15/download`
Caused by:
[35] SSL connect error (schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.)
C:\projects\rbpf>if 101 NEQ 0 exit 1
Command exited with code 1
Best related resource I could find is rust-lang/cargo#9788 - But if I read correctly, this should be fixed with recent cargo versions (1.55+, we're using 1.64+), so I'm not sure what's causing those flakes and how to fix them. Any suggestion welcome.
The following line:
Line 481 in ceb7fa6
Should probably be:
ebpf::MOV32_IMM => reg[_dst] = insn.imm as u32 as u64,
Instead of panicking, we should return 0 or dividend as a result of division or modulus by 0.
See kernel docs, and also iovisor/ubpf#116.
I'm new in BPF. Does this rbpf support kprobe and uprobe? If support uprobe, does it has context switch issues?
Callers may not want to panic if there is a problem running the program and instead may want to exit gracefully and report an error.
Consider changing:
pub fn prog_exec(&self, mem: &[u8], mbuff: &[u8]) -> u64
To:
pub fn prog_exec(&self, mem: &[u8], mbuff: &[u8]) -> Result<u64, Error>
I pushed a branch called patch_prog
that introduces a new module (patch
) that proposes a function attempting to patch an eBPF program generated with clang, in order to make it compatible with rbpf.
The function is a dumb heuristic and is nowhere close to what happens in the kernel. See comments in the source code (module patch
) for details. Implementing a more efficient algorithm does not look trivial (code in the kernel is GPL and we cannot reuse it).
I only tested it on a single example. If anyone has time to play with it and provide suggestions or feedback, that would be very welcome. Will try to update this issue if I can obtain better results.
In the implementation of MOD32
, it checks whether the 64-bit source value is zero. However, during the modulus computation, the source is casted to 32-bit, which could still be zero, e.g., 0xffff0000. According to the kernel ebpf standard, we should check the modulo by zero
by checking the nullness of the 32-bit source value.
If execution would result in modulo by zero, for
BPF_ALU64
the value of
the destination register is unchanged whereas forBPF_ALU
the upper
32 bits of the destination register are zeroed.
The current implementation of MOD32_IMM
and MOD32_REG
lacks the check to the lower 32 bits of source value.
Lines 230 to 233 in 4812c52
The following PoC program would cause the attempt to calculate the remainder with a divisor of zero
panic in the interpreter:
lddw r1, 0x100000000
mod32 r0, r1
exit
Result:
attempt to calculate the remainder with a divisor of zero
thread '' panicked at 'attempt to calculate the remainder with a divisor of zero', src/interpreter.rs:235:47
stack backtrace:
...
3: rbpf::interpreter::execute_program
at ./src/interpreter.rs:235:47
4: rbpf::EbpfVmMbuff::execute_program
at ./src/lib.rs:300:9
5: rbpf::EbpfVmRaw::execute_program
at ./src/lib.rs:1221:9
6: rbpf::EbpfVmNoData::execute_program
at ./src/lib.rs:1581:9
Check nullness of 32-bit casted source value before modulus
The verification function is coded directly in rbpf. Providing a way for the caller to override the default verifier with their own would provide a lot of flexibility and less need for others for fork and customize this repository for their own needs
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.