Skip to content

Commit df4b1ee

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-fix-and-test-aux-usage-after-do_check_insn'
Luis Gerhorst says: ==================== bpf: Fix and test aux usage after do_check_insn() Fix cur_aux()->nospec_result test after do_check_insn() referring to the to-be-analyzed (potentially unsafe) instruction, not the already-analyzed (safe) instruction. This might allow a unsafe insn to slip through on a speculative path. Create some tests from the reproducer [1]. Commit d6f1c85 ("bpf: Fall back to nospec for Spectre v1") should not be in any stable kernel yet, therefore bpf-next should suffice. [1] https://lore.kernel.org/bpf/[email protected]/ Changes since v2: - Use insn_aux variable instead of introducing prev_aux() as suggested by Eduard (and therefore also drop patch 1) - v2: https://lore.kernel.org/bpf/[email protected]/ Changes since v1: - Fix compiler error due to missed rename of prev_insn_idx in first patch - v1: https://lore.kernel.org/bpf/[email protected]/ Changes since RFC: - Introduce prev_aux() as suggested by Alexei. For this, we must move the env->prev_insn_idx assignment to happen directly after do_check_insn(), for which I have created a separate commit. This patch could be simplified by using a local prev_aux variable as sugested by Eduard, but I figured one might find the new assignment-strategy easier to understand (before, prev_insn_idx and env->prev_insn_idx were out-of-sync for the latter part of the loop). Also, like this we do not have an additional prev_* variable that must be kept in-sync and the local variable's usage (old prev_insn_idx, new tmp) is much more local. If you think it would be better to not take the risk and keep the fix simple by just introducing the prev_aux variable, let me know. - Change WARN_ON_ONCE() to verifier_bug_if() as suggested by Alexei - Change assertion to check instruction is BPF_JMP[32] as suggested by Eduard - RFC: https://lore.kernel.org/bpf/[email protected]/ ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents 0f626c9 + 92974ce commit df4b1ee

File tree

3 files changed

+167
-5
lines changed

3 files changed

+167
-5
lines changed

kernel/bpf/verifier.c

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19953,6 +19953,7 @@ static int do_check(struct bpf_verifier_env *env)
1995319953

1995419954
for (;;) {
1995519955
struct bpf_insn *insn;
19956+
struct bpf_insn_aux_data *insn_aux;
1995619957
int err;
1995719958

1995819959
/* reset current history entry on each new instruction */
@@ -19966,6 +19967,7 @@ static int do_check(struct bpf_verifier_env *env)
1996619967
}
1996719968

1996819969
insn = &insns[env->insn_idx];
19970+
insn_aux = &env->insn_aux_data[env->insn_idx];
1996919971

1997019972
if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
1997119973
verbose(env,
@@ -20042,19 +20044,19 @@ static int do_check(struct bpf_verifier_env *env)
2004220044
/* Reduce verification complexity by stopping speculative path
2004320045
* verification when a nospec is encountered.
2004420046
*/
20045-
if (state->speculative && cur_aux(env)->nospec)
20047+
if (state->speculative && insn_aux->nospec)
2004620048
goto process_bpf_exit;
2004720049

2004820050
err = do_check_insn(env, &do_print_state);
2004920051
if (error_recoverable_with_nospec(err) && state->speculative) {
2005020052
/* Prevent this speculative path from ever reaching the
2005120053
* insn that would have been unsafe to execute.
2005220054
*/
20053-
cur_aux(env)->nospec = true;
20055+
insn_aux->nospec = true;
2005420056
/* If it was an ADD/SUB insn, potentially remove any
2005520057
* markings for alu sanitization.
2005620058
*/
20057-
cur_aux(env)->alu_state = 0;
20059+
insn_aux->alu_state = 0;
2005820060
goto process_bpf_exit;
2005920061
} else if (err < 0) {
2006020062
return err;
@@ -20063,7 +20065,7 @@ static int do_check(struct bpf_verifier_env *env)
2006320065
}
2006420066
WARN_ON_ONCE(err);
2006520067

20066-
if (state->speculative && cur_aux(env)->nospec_result) {
20068+
if (state->speculative && insn_aux->nospec_result) {
2006720069
/* If we are on a path that performed a jump-op, this
2006820070
* may skip a nospec patched-in after the jump. This can
2006920071
* currently never happen because nospec_result is only
@@ -20072,8 +20074,15 @@ static int do_check(struct bpf_verifier_env *env)
2007220074
* never skip the following insn. Still, add a warning
2007320075
* to document this in case nospec_result is used
2007420076
* elsewhere in the future.
20077+
*
20078+
* All non-branch instructions have a single
20079+
* fall-through edge. For these, nospec_result should
20080+
* already work.
2007520081
*/
20076-
WARN_ON_ONCE(env->insn_idx != prev_insn_idx + 1);
20082+
if (verifier_bug_if(BPF_CLASS(insn->code) == BPF_JMP ||
20083+
BPF_CLASS(insn->code) == BPF_JMP32, env,
20084+
"speculation barrier after jump instruction may not have the desired effect"))
20085+
return -EFAULT;
2007720086
process_bpf_exit:
2007820087
mark_verifier_state_scratched(env);
2007920088
err = update_branch_counts(env, env->cur_state);

tools/testing/selftests/bpf/progs/bpf_misc.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,8 @@
237237
#define SPEC_V1
238238
#endif
239239

240+
#if defined(__TARGET_ARCH_x86)
241+
#define SPEC_V4
242+
#endif
243+
240244
#endif

tools/testing/selftests/bpf/progs/verifier_unpriv.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,4 +801,153 @@ l2_%=: \
801801
: __clobber_all);
802802
}
803803

804+
SEC("socket")
805+
__description("unpriv: ldimm64 before Spectre v4 barrier")
806+
__success __success_unpriv
807+
__retval(0)
808+
#ifdef SPEC_V4
809+
__xlated_unpriv("r1 = 0x2020200005642020") /* should not matter */
810+
__xlated_unpriv("*(u64 *)(r10 -8) = r1")
811+
__xlated_unpriv("nospec")
812+
#endif
813+
__naked void unpriv_ldimm64_spectre_v4(void)
814+
{
815+
asm volatile (" \
816+
r1 = 0x2020200005642020 ll; \
817+
*(u64 *)(r10 -8) = r1; \
818+
r0 = 0; \
819+
exit; \
820+
" ::: __clobber_all);
821+
}
822+
823+
SEC("socket")
824+
__description("unpriv: Spectre v1 and v4 barrier")
825+
__success __success_unpriv
826+
__retval(0)
827+
#ifdef SPEC_V1
828+
#ifdef SPEC_V4
829+
/* starts with r0 == r8 == r9 == 0 */
830+
__xlated_unpriv("if r8 != 0x0 goto pc+1")
831+
__xlated_unpriv("goto pc+2")
832+
__xlated_unpriv("if r9 == 0x0 goto pc+4")
833+
__xlated_unpriv("r2 = r0")
834+
/* Following nospec required to prevent following dangerous `*(u64 *)(NOT_FP -64)
835+
* = r1` iff `if r9 == 0 goto pc+4` was mispredicted because of Spectre v1. The
836+
* test therefore ensures the Spectre-v4--induced nospec does not prevent the
837+
* Spectre-v1--induced speculative path from being fully analyzed.
838+
*/
839+
__xlated_unpriv("nospec") /* Spectre v1 */
840+
__xlated_unpriv("*(u64 *)(r2 -64) = r1") /* could be used to leak r2 */
841+
__xlated_unpriv("nospec") /* Spectre v4 */
842+
#endif
843+
#endif
844+
__naked void unpriv_spectre_v1_and_v4(void)
845+
{
846+
asm volatile (" \
847+
r1 = 0; \
848+
*(u64*)(r10 - 8) = r1; \
849+
r2 = r10; \
850+
r2 += -8; \
851+
r1 = %[map_hash_8b] ll; \
852+
call %[bpf_map_lookup_elem]; \
853+
r8 = r0; \
854+
r2 = r10; \
855+
r2 += -8; \
856+
r1 = %[map_hash_8b] ll; \
857+
call %[bpf_map_lookup_elem]; \
858+
r9 = r0; \
859+
r0 = r10; \
860+
r1 = 0; \
861+
r2 = r10; \
862+
if r8 != 0 goto l0_%=; \
863+
if r9 != 0 goto l0_%=; \
864+
r0 = 0; \
865+
l0_%=: if r8 != 0 goto l1_%=; \
866+
goto l2_%=; \
867+
l1_%=: if r9 == 0 goto l3_%=; \
868+
r2 = r0; \
869+
l2_%=: *(u64 *)(r2 -64) = r1; \
870+
l3_%=: r0 = 0; \
871+
exit; \
872+
" :
873+
: __imm(bpf_map_lookup_elem),
874+
__imm_addr(map_hash_8b)
875+
: __clobber_all);
876+
}
877+
878+
SEC("socket")
879+
__description("unpriv: Spectre v1 and v4 barrier (simple)")
880+
__success __success_unpriv
881+
__retval(0)
882+
#ifdef SPEC_V1
883+
#ifdef SPEC_V4
884+
__xlated_unpriv("if r8 != 0x0 goto pc+1")
885+
__xlated_unpriv("goto pc+2")
886+
__xlated_unpriv("goto pc-1") /* if r9 == 0 goto l3_%= */
887+
__xlated_unpriv("goto pc-1") /* r2 = r0 */
888+
__xlated_unpriv("nospec")
889+
__xlated_unpriv("*(u64 *)(r2 -64) = r1")
890+
__xlated_unpriv("nospec")
891+
#endif
892+
#endif
893+
__naked void unpriv_spectre_v1_and_v4_simple(void)
894+
{
895+
asm volatile (" \
896+
r8 = 0; \
897+
r9 = 0; \
898+
r0 = r10; \
899+
r1 = 0; \
900+
r2 = r10; \
901+
if r8 != 0 goto l0_%=; \
902+
if r9 != 0 goto l0_%=; \
903+
r0 = 0; \
904+
l0_%=: if r8 != 0 goto l1_%=; \
905+
goto l2_%=; \
906+
l1_%=: if r9 == 0 goto l3_%=; \
907+
r2 = r0; \
908+
l2_%=: *(u64 *)(r2 -64) = r1; \
909+
l3_%=: r0 = 0; \
910+
exit; \
911+
" ::: __clobber_all);
912+
}
913+
914+
SEC("socket")
915+
__description("unpriv: ldimm64 before Spectre v1 and v4 barrier (simple)")
916+
__success __success_unpriv
917+
__retval(0)
918+
#ifdef SPEC_V1
919+
#ifdef SPEC_V4
920+
__xlated_unpriv("if r8 != 0x0 goto pc+1")
921+
__xlated_unpriv("goto pc+4")
922+
__xlated_unpriv("goto pc-1") /* if r9 == 0 goto l3_%= */
923+
__xlated_unpriv("goto pc-1") /* r2 = r0 */
924+
__xlated_unpriv("goto pc-1") /* r1 = 0x2020200005642020 ll */
925+
__xlated_unpriv("goto pc-1") /* second part of ldimm64 */
926+
__xlated_unpriv("nospec")
927+
__xlated_unpriv("*(u64 *)(r2 -64) = r1")
928+
__xlated_unpriv("nospec")
929+
#endif
930+
#endif
931+
__naked void unpriv_ldimm64_spectre_v1_and_v4_simple(void)
932+
{
933+
asm volatile (" \
934+
r8 = 0; \
935+
r9 = 0; \
936+
r0 = r10; \
937+
r1 = 0; \
938+
r2 = r10; \
939+
if r8 != 0 goto l0_%=; \
940+
if r9 != 0 goto l0_%=; \
941+
r0 = 0; \
942+
l0_%=: if r8 != 0 goto l1_%=; \
943+
goto l2_%=; \
944+
l1_%=: if r9 == 0 goto l3_%=; \
945+
r2 = r0; \
946+
r1 = 0x2020200005642020 ll; \
947+
l2_%=: *(u64 *)(r2 -64) = r1; \
948+
l3_%=: r0 = 0; \
949+
exit; \
950+
" ::: __clobber_all);
951+
}
952+
804953
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)