-
Notifications
You must be signed in to change notification settings - Fork 156
Fix helper nospec #10797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
luisgerhorst
wants to merge
6
commits into
kernel-patches:bpf-next_base
Choose a base branch
from
luisgerhorst:fix-helper-nospec
base: bpf-next_base
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Fix helper nospec #10797
luisgerhorst
wants to merge
6
commits into
kernel-patches:bpf-next_base
from
luisgerhorst:fix-helper-nospec
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
c8631f1 to
cd64934
Compare
The BPF verifier assumes `insn_aux->nospec_result` is only set for
direct memory writes (e.g., `*(u32*)(r1+off) = r2`). However, the
assertion fails to account for helper calls (e.g.,
`bpf_skb_load_bytes_relative`) that perform writes to stack memory. Make
the check more precise to resolve this.
The problem is that `BPF_CALL` instructions have `BPF_CLASS(insn->code)
== BPF_JMP`, which triggers the warning check:
- Helpers like `bpf_skb_load_bytes_relative` write to stack memory
- `check_helper_call()` loops through `meta.access_size`, calling
`check_mem_access(..., BPF_WRITE)`
- `check_stack_write()` sets `insn_aux->nospec_result = 1`
- Since `BPF_CALL` is encoded as `BPF_JMP | BPF_CALL`, the warning fires
Execution flow:
```
1. Drop capabilities → Enable Spectre mitigation
2. Load BPF program
└─> do_check()
├─> check_cond_jmp_op() → Marks dead branch as speculative
│ └─> push_stack(..., speculative=true)
├─> pop_stack() → state->speculative = 1
├─> check_helper_call() → Processes helper in dead branch
│ └─> check_mem_access(..., BPF_WRITE)
│ └─> insn_aux->nospec_result = 1
└─> Checks: state->speculative && insn_aux->nospec_result
└─> BPF_CLASS(insn->code) == BPF_JMP → WARNING
```
To fix the assert, it would be nice to be able to reuse
bpf_insn_successors() here, but bpf_insn_successors()->cnt is not
exactly what we want as it may also be 1 for BPF_JA. Instead, we could
check opcode_info.can_jump, but then we would have to share the table
between the functions. This would mean moving the table out of the
function and adding bpf_opcode_info(). As the verifier_bug_if() only
runs for insns with nospec_result set, the impact on verification time
would likely still be negligible. However, I assume sharing
bpf_opcode_info() between liveness.c and verifier.c will not be worth
it. It seems as only adjust_jmp_off() could also be simplified using it,
and there imm/off is touched. Thus it is maybe better to rely on exact
opcode/class matching there.
Therefore, to avoid this sharing only for a verifier_bug_if(), just
check the opcode. This should now cover all opcodes for which can_jump
in bpf_insn_successors() is true.
Parts of the description and example are taken from the bug report.
Signed-off-by: Luis Gerhorst <luis.gerhorst@fau.de>
Reported-by: Yinhao Hu <dddddd@hust.edu.cn>
Reported-by: Kaiyan Mei <M202472210@hust.edu.cn>
Reported-by: Dongliang Mu <dzm91@hust.edu.cn>
Link: https://lore.kernel.org/bpf/7678017d-b760-4053-a2d8-a6879b0dbeeb@hust.edu.cn/
Without the fix from the previous commit, the selftest fails: $ ./tools/testing/selftests/bpf/vmtest.sh -- ./test_progs -t verifier_unpriv [...] run_subtest:PASS:obj_open_mem 0 nsec libbpf: BTF loading error: -EPERM libbpf: Error loading .BTF into kernel: -EPERM. BTF is optional, ignoring. libbpf: prog 'unpriv_nospec_after_helper_stack_write': BPF program load failed: -EFAULT libbpf: prog 'unpriv_nospec_after_helper_stack_write': failed to load: -EFAULT libbpf: failed to load object 'verifier_unpriv' run_subtest:FAIL:unexpected_load_failure unexpected error: -14 (errno 14) VERIFIER LOG: ============= 0: R1=ctx() R10=fp0 0: (b7) r0 = 0 ; R0=P0 1: (55) if r0 != 0x1 goto pc+6 2: R0=Pscalar() R1=ctx() R10=fp0 2: (b7) r2 = 0 ; R2=P0 3: (bf) r3 = r10 ; R3=fp0 R10=fp0 4: (07) r3 += -16 ; R3=fp-16 5: (b7) r4 = 4 ; R4=P4 6: (b7) r5 = 0 ; R5=P0 7: (85) call bpf_skb_load_bytes_relative#68 verifier bug: speculation barrier after jump instruction may not have the desired effect (BPF_CLASS(insn->code) == BPF_JMP || BPF_CLASS(insn->code) == BPF_JMP32) processed 9 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 ============= [...] The test is based on the PoC from the report. Signed-off-by: Luis Gerhorst <luis.gerhorst@fau.de> Reported-by: Yinhao Hu <dddddd@hust.edu.cn> Reported-by: Kaiyan Mei <M202472210@hust.edu.cn> Reported-by: Dongliang Mu <dzm91@hust.edu.cn> Link: https://lore.kernel.org/bpf/7678017d-b760-4053-a2d8-a6879b0dbeeb@hust.edu.cn/
Without this, a SSB might be possible by filling up the store port and the calling a helper that initializes the stack. If the store in the helper is delayed due to the port contention, the eBPF program may speculatively read and leak stale kernel data previously stored at the stack slot. I did not attempt to actually develop a exploit to test whether this is possible with any concrete compiler/CPU I have. However, as the overhead will be small compared to the Spectre v4 mitigations we already have, I propose to be conservative and also add the nospec for stack-initializing calls. It is certainly harder to exploit than a SSB in the program directly because the other things in the function will reduce the size of the speculation window. However, it seems possible assuming the CPU speculates out of calls. As a call is much less common than a regular stack initialization, the overhead will be very small compared to the SSB mitigations already in place. If performance is a concern for unpriv eBPF, I think it will be more worthwile to optimize these. Implementation: - An alternative to introducing keep_insn would be to create a separate loop only for adding nospec[_result]. I assume we want to avoid the overhead. - Add a verifier_bug_if() for ldimm64 in bpf_patch_insn_data() to make sure we catch it if nospec_result ever is set for ldimm64. - We could also avoid the bpf_patch_insn_data() call if we have reach keep_insn without nospec_result set, but this would further complicate the control flow. As bpf_patch_insn_single() does not actually reallocate if the patch size is 1, do not attempt to optimize this further. Signed-off-by: Luis Gerhorst <luis.gerhorst@fau.de>
Add test that checks if a nospec is added after the calls that initialize stack slots (which fails without the fix in the previos commit). Signed-off-by: Luis Gerhorst <luis.gerhorst@fau.de>
cd64934 to
6c3db12
Compare
dc9fd0a to
5e38e6f
Compare
cd8cbf1 to
358bea9
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
No description provided.