eBPF Can't Count?
blog.cloudflare.com
blog.cloudflare.com
$ clang -O2 -target bpf -Xclang -target-feature -Xclang +alu32 -c sub64.c -o - | llvm-objdump -S -
> Apparently the compiler decided it was better to operate on 64-bit registers and discard the upper 32 bits.The workaround was to use the `volatile` keyword.
The problem kind of sounds like one of the LLVM optimisation passes made the change.
http://releases.llvm.org/8.0.0/docs/Passes.html#transform-pa...
Wonder if disabling optimisations ("-O0") would have also worked?
$ clang -O2 -target bpf -Xclang -target-feature -Xclang +alu32 -c sub32.c
$ llvm-objdump -S -no-show-raw-insn sub32.o
sub32.o: file format ELF64-BPF
Disassembly of section .text:
0000000000000000 sub32:
0: w0 = w1
1: w0 -= w2
2: exit
$ clang -O0 -target bpf -Xclang -target-feature -Xclang +alu32 -c sub32.c
$ llvm-objdump -S -no-show-raw-insn sub32.o
sub32.o: file format ELF64-BPF
Disassembly of section .text:
0000000000000000 sub32:
0: w3 = w2
1: w4 = w1
2: *(u32 *)(r10 - 4) = r1
3: *(u32 *)(r10 - 8) = r2
4: r1 = *(u32 *)(r10 - 4)
5: r2 = *(u32 *)(r10 - 8)
6: w1 -= w2
7: w0 = w1
8: *(u32 *)(r10 - 12) = r3
9: *(u32 *)(r10 - 16) = r4
10: exitBasically, it's impossible to use clang to generate eBPF without -O2. Sorry.
That's a bit confusing, as the comment by praseodym shows the output from doing just that.
???
The idea is to compile to assembly which both fixes the bug, and is runnable by eBPF.
- Clang can compile C
- eBPF backend can generate the eBPF bytecode for subset of IR (AFAIU not all IR, not all "C" is okay for eBPF)
- verifier can accept or reject the generated eBPF.
The goal is to write C in such a way that will be generally sensible C but that will consistently yield loadable eBPF programs. This is not easy! Just recently I was debugging code where clang optmizer decided to merge two loads of 2 bytes into one load of 4 bytes. This made the verifier unhappy - the specific loads are rewritten to helper calls by verifier. The verifier knows what to do on 2-byte load at offset X, and 2-byte load at offset X+2. But it has no idea what to do with 4-byte load at offset X!
Writing C programs that will compile to valid, loadable eBPF requires a fair amount of voodoo.
Kind of sounds like having a specific eBPF transformation pass available in LLVM would be useful.
eg having it generate known-to-work-with-eBPF output
As majke mentioned we need optimizations or the backend compiler will complain:
$ clang -O0 -target bpf filter_alu32.c -emit-llvm -S -c -o filter_alu32.ll
$ llc -march=bpf -mattr=+alu32 -filetype=obj filter_alu32.ll
LLVM ERROR: Unsupported relocation: try to compile with -O2 or above, or check your static variable usage
The code has to be non-trivial to hit the error. I think its the use of eBPF maps that triggers it.What if we turned off the optimizations in the frontend and ran just the backend optimizer, though? I had some success with this approach:
$ clang -O0 -target bpf filter_alu32.c -emit-llvm -S -c -o filter_alu32.ll
$ opt -O2 filter_alu32.ll | llc -march=bpf -mattr=+alu32 -filetype=obj -o filter_alu32.o
$ llvm-objdump -S filter_alu32.o | grep -e -=
45: 1c 21 00 00 00 00 00 00 w1 -= w2
53: 1c 21 00 00 00 00 00 00 w1 -= w2
65: 1c 21 00 00 00 00 00 00 w1 -= w2
You end up with more eBPF instructions if you don't use the frontend optimizer but the code doesn't need any hacks. Perhaps it can be tweaked further by applying frontend optimizations selectively?So thanks for the suggestion. It's another working solution.
You can find the code for this experiment here: https://github.com/jsitnicki/cloudflare-blog/commit/acf28b69...
I've been doing some eBPF work at the moment but the CloudFlare guys are at another level.
I'd love to take it up a notch like them. eBPF is very exciting and I'm hoping to remain in the space.
The other problem is that eBPF is a far way from KISS. But then that’s what’s needed for power and performance.
EDIT after RTFA: and security against spectre and friends.
EDIT: I’m curious if a down voter can provide a piece of code that tests whether two compilates are equivalent.
The root cause of the bug in the article was introduced by a patch to automatically modify an arbitrary program to harden against spectre.
assert func(params) == rewrite(func)(params)
for some reasonable set of func and params. Totally enough to catch problems like this. I'd say [unit-]testing that subtraction operator works is not that hard.It would help the discussion if you read the TFA (edit: more carefully).
Rewrite() in this case modified the params program to harden it against Spectre.
What you do is put together a long list of sample functions and sample arguments that covers the expected edge cases and then test those for equivalence. Hardly impossible. Just takes time. Not bulletproof but better than nothing.
You can just run the original and rewritten code in an eBPF VM with mock inputs and compare.
Sadly, the tests aren't really worth much here - the bug was in the _kernel_ not the eBPF code! Once again - we'd need to run our tests on the affected kernels to see the problem. We're not there yet, our automatic testing infra runs on another set of kernels than production. Oops on our side.
But the fact is - testing new eBPF features is hard. You need to run a rich eBPF bytecode, on a modern kernel. I think the proper approach is to run usermode linux, and run tests _inside_ of it. Not sure how hard would that be to pull it off though.
Approach similar to this project: https://source.android.com/devices/architecture/kernel/netwo...
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
Tests for the corner case that has caused the problem have been added shortly after it was fixed:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...