Skip to content

Commit caedc19

Browse files
luisgerhorstKernel Patches Daemon
authored andcommitted
bpf: Fall back to nospec for sanitization-failures
ALU sanitization was introduced to ensure that a subsequent ptr access can never go OOB, even under speculation. This is required because we currently allow speculative scalar confusion. Spec. scalar confusion is possible because Spectre v4 sanitization only adds a nospec after critical stores (e.g., scalar overwritten with a pointer). If we add a nospec before the ALU op, none of the operands can be subject to scalar confusion. As an ADD/SUB can not introduce scalar confusion itself, the result will also not be subject to scalar confusion. Therefore, the subsequent ptr access is always safe. We directly fall back to nospec for the sanitization errors REASON_BOUNDS, _TYPE, _PATHS, and _LIMIT, even if we are not on a speculative path. For REASON_STACK, we return the error directly now. Decided to directly set cur_aux(env)->nospec to implement the fallback instead of (conceptually) making the nospec part of the ALU sanitization state and therefore potentially dragging it through info.aux before copying it over into cur_aux(env). This has the drawback that the usage of cur_aux(env) and aux in these functions might be confusing, but it has the upside that it does not needlessly complicate the dataflow for nospec. Also the presence of cur_aux() might make it more obvious that aux might not equal cur_aux() here. In the commit window, sanitize_ptr_alu() will bail out early because can_skip_alu_sanitation() checks cur_aux(env)->nospec. Regarding commit 97744b4 ("bpf: Clarify sanitize_check_bounds()"), having the assertion trigger if alu_state is set on a non-speculative path makes the most sense, because the masking would truncate the bounds on that path when executed and sanitize_check_bounds() exists to ensure this trucation does not happen. Two cases are relevant: - First, if a case in sanitize_check_bounds() is omitted, it fails with EOPNOTSUPP but retrieve_ptr_limit() returns 0 (thereby potentially not setting cur_aux(env)->nospec instead of setting alu_state). With the old/new assertion, this is cought. - Second, if a case is omitted from retrieve_ptr_limit() but not from sanitize_check_bounds(), bounds_ret equals 0 or -EACCES. If it is 0 and the default case in retrieve_ptr_limit() runs errorously, we mark the insn for nospec-sanitization. This is not really a security problem and it could also only be detected by adding a verifier_bug_if(bounds_ret != -EOPNOTSUPP) into the default case in retrieve_ptr_limit(). It is not cought by the old/new assertion. Because it remove the possibility for these errors altogether, this change also fixes some unwarranted test failures on architectures that have bpf_jit_bypass_spec_v1 set (e.g., LoongArch). Signed-off-by: Luis Gerhorst <[email protected]> Acked-by: Kumar Kartikeya Dwivedi <[email protected]> Acked-by: Henriette Herzog <[email protected]> Cc: Hengqi Chen <[email protected]>
1 parent ce3f403 commit caedc19

File tree

5 files changed

+180
-115
lines changed

5 files changed

+180
-115
lines changed

kernel/bpf/verifier.c

Lines changed: 50 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14195,14 +14195,6 @@ static bool check_reg_sane_offset(struct bpf_verifier_env *env,
1419514195
return true;
1419614196
}
1419714197

14198-
enum {
14199-
REASON_BOUNDS = -1,
14200-
REASON_TYPE = -2,
14201-
REASON_PATHS = -3,
14202-
REASON_LIMIT = -4,
14203-
REASON_STACK = -5,
14204-
};
14205-
1420614198
static int retrieve_ptr_limit(const struct bpf_reg_state *ptr_reg,
1420714199
u32 *alu_limit, bool mask_to_left)
1420814200
{
@@ -14225,11 +14217,13 @@ static int retrieve_ptr_limit(const struct bpf_reg_state *ptr_reg,
1422514217
ptr_reg->umax_value) + ptr_reg->off;
1422614218
break;
1422714219
default:
14228-
return REASON_TYPE;
14220+
/* Register has pointer with unsupported alu operation. */
14221+
return -EOPNOTSUPP;
1422914222
}
1423014223

14224+
/* Register tried access beyond pointer bounds. */
1423114225
if (ptr_limit >= max)
14232-
return REASON_LIMIT;
14226+
return -EOPNOTSUPP;
1423314227
*alu_limit = ptr_limit;
1423414228
return 0;
1423514229
}
@@ -14242,32 +14236,38 @@ static bool can_skip_alu_sanitation(const struct bpf_verifier_env *env,
1424214236
cur_aux(env)->nospec;
1424314237
}
1424414238

14245-
static int update_alu_sanitation_state(struct bpf_insn_aux_data *aux,
14246-
u32 alu_state, u32 alu_limit)
14239+
static void update_alu_sanitation_state(struct bpf_verifier_env *env,
14240+
struct bpf_insn_aux_data *aux,
14241+
u32 alu_state, u32 alu_limit)
1424714242
{
1424814243
/* If we arrived here from different branches with different
1424914244
* state or limits to sanitize, then this won't work.
1425014245
*/
1425114246
if (aux->alu_state &&
1425214247
(aux->alu_state != alu_state ||
14253-
aux->alu_limit != alu_limit))
14254-
return REASON_PATHS;
14248+
aux->alu_limit != alu_limit)) {
14249+
/* Tried to perform alu op from different maps, paths or
14250+
* scalars.
14251+
*/
14252+
cur_aux(env)->nospec = true;
14253+
cur_aux(env)->alu_state = 0;
14254+
return;
14255+
}
1425514256

1425614257
/* Corresponding fixup done in do_misc_fixups(). */
1425714258
aux->alu_state = alu_state;
1425814259
aux->alu_limit = alu_limit;
14259-
return 0;
1426014260
}
1426114261

14262-
static int sanitize_val_alu(struct bpf_verifier_env *env,
14263-
struct bpf_insn *insn)
14262+
static void sanitize_val_alu(struct bpf_verifier_env *env,
14263+
struct bpf_insn *insn)
1426414264
{
1426514265
struct bpf_insn_aux_data *aux = cur_aux(env);
1426614266

1426714267
if (can_skip_alu_sanitation(env, insn))
14268-
return 0;
14268+
return;
1426914269

14270-
return update_alu_sanitation_state(aux, BPF_ALU_NON_POINTER, 0);
14270+
update_alu_sanitation_state(env, aux, BPF_ALU_NON_POINTER, 0);
1427114271
}
1427214272

1427314273
static bool sanitize_needed(u8 opcode)
@@ -14332,16 +14332,29 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
1433214332

1433314333
if (!commit_window) {
1433414334
if (!tnum_is_const(off_reg->var_off) &&
14335-
(off_reg->smin_value < 0) != (off_reg->smax_value < 0))
14336-
return REASON_BOUNDS;
14335+
(off_reg->smin_value < 0) != (off_reg->smax_value < 0)) {
14336+
/* Register has unknown scalar with mixed signed
14337+
* bounds.
14338+
*/
14339+
cur_aux(env)->alu_state = 0;
14340+
cur_aux(env)->nospec = true;
14341+
return 0;
14342+
}
1433714343

1433814344
info->mask_to_left = (opcode == BPF_ADD && off_is_neg) ||
1433914345
(opcode == BPF_SUB && !off_is_neg);
1434014346
}
1434114347

1434214348
err = retrieve_ptr_limit(ptr_reg, &alu_limit, info->mask_to_left);
14343-
if (err < 0)
14344-
return err;
14349+
if (err) {
14350+
if (verifier_bug_if(err != -EOPNOTSUPP, env,
14351+
"Fall back to BPF_NOSPEC for error %d might not be safe",
14352+
err))
14353+
return -EFAULT;
14354+
cur_aux(env)->alu_state = 0;
14355+
cur_aux(env)->nospec = true;
14356+
return 0;
14357+
}
1434514358

1434614359
if (commit_window) {
1434714360
/* In commit phase we narrow the masking window based on
@@ -14362,9 +14375,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
1436214375
env->explore_alu_limits = true;
1436314376
}
1436414377

14365-
err = update_alu_sanitation_state(aux, alu_state, alu_limit);
14366-
if (err < 0)
14367-
return err;
14378+
update_alu_sanitation_state(env, aux, alu_state, alu_limit);
1436814379
do_sim:
1436914380
/* If we're in commit phase, we're done here given we already
1437014381
* pushed the truncated dst_reg into the speculative verification
@@ -14373,8 +14384,9 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
1437314384
* Also, when register is a known constant, we rewrite register-based
1437414385
* operation to immediate-based, and thus do not need masking (and as
1437514386
* a consequence, do not need to simulate the zero-truncation either).
14387+
* Same is true if we did a fallback to a nospec.
1437614388
*/
14377-
if (commit_window || off_is_imm)
14389+
if (commit_window || off_is_imm || cur_aux(env)->nospec)
1437814390
return 0;
1437914391

1438014392
/* Simulate and find potential out-of-bounds access under
@@ -14394,7 +14406,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
1439414406
env->insn_idx);
1439514407
if (!ptr_is_dst_reg && ret)
1439614408
*dst_reg = tmp;
14397-
return !ret ? REASON_STACK : 0;
14409+
return !ret ? -ENOMEM : 0;
1439814410
}
1439914411

1440014412
static void sanitize_mark_insn_seen(struct bpf_verifier_env *env)
@@ -14410,44 +14422,6 @@ static void sanitize_mark_insn_seen(struct bpf_verifier_env *env)
1441014422
env->insn_aux_data[env->insn_idx].seen = env->pass_cnt;
1441114423
}
1441214424

14413-
static int sanitize_err(struct bpf_verifier_env *env,
14414-
const struct bpf_insn *insn, int reason,
14415-
const struct bpf_reg_state *off_reg,
14416-
const struct bpf_reg_state *dst_reg)
14417-
{
14418-
static const char *err = "pointer arithmetic with it prohibited for !root";
14419-
const char *op = BPF_OP(insn->code) == BPF_ADD ? "add" : "sub";
14420-
u32 dst = insn->dst_reg, src = insn->src_reg;
14421-
14422-
switch (reason) {
14423-
case REASON_BOUNDS:
14424-
verbose(env, "R%d has unknown scalar with mixed signed bounds, %s\n",
14425-
off_reg == dst_reg ? dst : src, err);
14426-
break;
14427-
case REASON_TYPE:
14428-
verbose(env, "R%d has pointer with unsupported alu operation, %s\n",
14429-
off_reg == dst_reg ? src : dst, err);
14430-
break;
14431-
case REASON_PATHS:
14432-
verbose(env, "R%d tried to %s from different maps, paths or scalars, %s\n",
14433-
dst, op, err);
14434-
break;
14435-
case REASON_LIMIT:
14436-
verbose(env, "R%d tried to %s beyond pointer bounds, %s\n",
14437-
dst, op, err);
14438-
break;
14439-
case REASON_STACK:
14440-
verbose(env, "R%d could not be pushed for speculative verification, %s\n",
14441-
dst, err);
14442-
return -ENOMEM;
14443-
default:
14444-
verifier_bug(env, "unknown reason (%d)", reason);
14445-
break;
14446-
}
14447-
14448-
return -EACCES;
14449-
}
14450-
1445114425
/* check that stack access falls within stack limits and that 'reg' doesn't
1445214426
* have a variable offset.
1445314427
*
@@ -14620,7 +14594,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
1462014594
ret = sanitize_ptr_alu(env, insn, ptr_reg, off_reg, dst_reg,
1462114595
&info, false);
1462214596
if (ret < 0)
14623-
return sanitize_err(env, insn, ret, off_reg, dst_reg);
14597+
return ret;
1462414598
}
1462514599

1462614600
switch (opcode) {
@@ -14748,15 +14722,15 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
1474814722
if (sanitize_needed(opcode)) {
1474914723
ret = sanitize_ptr_alu(env, insn, dst_reg, off_reg, dst_reg,
1475014724
&info, true);
14751-
if (verifier_bug_if(!can_skip_alu_sanitation(env, insn)
14752-
&& !env->cur_state->speculative
14753-
&& bounds_ret
14754-
&& !ret,
14755-
env, "Pointer type unsupported by sanitize_check_bounds() not rejected by retrieve_ptr_limit() as required")) {
14756-
return -EFAULT;
14757-
}
1475814725
if (ret < 0)
14759-
return sanitize_err(env, insn, ret, off_reg, dst_reg);
14726+
return ret;
14727+
if (verifier_bug_if(cur_aux(env)->alu_state &&
14728+
cur_aux(env)->alu_state != BPF_ALU_NON_POINTER &&
14729+
!env->cur_state->speculative &&
14730+
bounds_ret, env,
14731+
"Pointer type unsupported by sanitize_check_bounds() (error %d) not rejected by retrieve_ptr_limit() as required",
14732+
bounds_ret))
14733+
return -EFAULT;
1476014734
}
1476114735

1476214736
return 0;
@@ -15385,9 +15359,7 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
1538515359
}
1538615360

1538715361
if (sanitize_needed(opcode)) {
15388-
ret = sanitize_val_alu(env, insn);
15389-
if (ret < 0)
15390-
return sanitize_err(env, insn, ret, NULL, NULL);
15362+
sanitize_val_alu(env, insn);
1539115363
}
1539215364

1539315365
/* Calculate sign/unsigned bounds and tnum for alu32 and alu64 bit ops.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ SEC("socket")
4848
__description("subtraction bounds (map value) variant 2")
4949
__failure
5050
__msg("R0 min value is negative, either use unsigned index or do a if (index >=0) check.")
51-
__msg_unpriv("R1 has unknown scalar with mixed signed bounds")
51+
__msg_unpriv("R0 pointer arithmetic of map value goes out of range, prohibited for !root")
5252
__naked void bounds_map_value_variant_2(void)
5353
{
54+
/* unpriv: nospec inserted to prevent "R1 has unknown scalar with mixed
55+
* signed bounds".
56+
*/
5457
asm volatile (" \
5558
r1 = 0; \
5659
*(u64*)(r10 - 8) = r1; \

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

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@
88
SEC("socket")
99
__description("check deducing bounds from const, 1")
1010
__failure __msg("R0 tried to subtract pointer from scalar")
11-
__msg_unpriv("R1 has pointer with unsupported alu operation")
11+
__failure_unpriv
1212
__naked void deducing_bounds_from_const_1(void)
1313
{
1414
asm volatile (" \
1515
r0 = 1; \
1616
if r0 s>= 1 goto l0_%=; \
17-
l0_%=: r0 -= r1; \
17+
l0_%=: /* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
18+
r0 -= r1; \
1819
exit; \
1920
" ::: __clobber_all);
2021
}
2122

2223
SEC("socket")
2324
__description("check deducing bounds from const, 2")
24-
__success __failure_unpriv
25-
__msg_unpriv("R1 has pointer with unsupported alu operation")
25+
__success __success_unpriv
2626
__retval(1)
27+
#ifdef SPEC_V1
28+
__xlated_unpriv("nospec") /* inserted to prevent `R1 has pointer with unsupported alu operation` */
29+
__xlated_unpriv("r1 -= r0")
30+
#endif
2731
__naked void deducing_bounds_from_const_2(void)
2832
{
2933
asm volatile (" \
@@ -40,22 +44,26 @@ l1_%=: r1 -= r0; \
4044
SEC("socket")
4145
__description("check deducing bounds from const, 3")
4246
__failure __msg("R0 tried to subtract pointer from scalar")
43-
__msg_unpriv("R1 has pointer with unsupported alu operation")
47+
__failure_unpriv
4448
__naked void deducing_bounds_from_const_3(void)
4549
{
4650
asm volatile (" \
4751
r0 = 0; \
4852
if r0 s<= 0 goto l0_%=; \
49-
l0_%=: r0 -= r1; \
53+
l0_%=: /* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
54+
r0 -= r1; \
5055
exit; \
5156
" ::: __clobber_all);
5257
}
5358

5459
SEC("socket")
5560
__description("check deducing bounds from const, 4")
56-
__success __failure_unpriv
57-
__msg_unpriv("R6 has pointer with unsupported alu operation")
61+
__success __success_unpriv
5862
__retval(0)
63+
#ifdef SPEC_V1
64+
__xlated_unpriv("nospec") /* inserted to prevent `R6 has pointer with unsupported alu operation` */
65+
__xlated_unpriv("r6 -= r0")
66+
#endif
5967
__naked void deducing_bounds_from_const_4(void)
6068
{
6169
asm volatile (" \
@@ -73,12 +81,13 @@ l1_%=: r6 -= r0; \
7381
SEC("socket")
7482
__description("check deducing bounds from const, 5")
7583
__failure __msg("R0 tried to subtract pointer from scalar")
76-
__msg_unpriv("R1 has pointer with unsupported alu operation")
84+
__failure_unpriv
7785
__naked void deducing_bounds_from_const_5(void)
7886
{
7987
asm volatile (" \
8088
r0 = 0; \
8189
if r0 s>= 1 goto l0_%=; \
90+
/* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
8291
r0 -= r1; \
8392
l0_%=: exit; \
8493
" ::: __clobber_all);
@@ -87,29 +96,31 @@ l0_%=: exit; \
8796
SEC("socket")
8897
__description("check deducing bounds from const, 6")
8998
__failure __msg("R0 tried to subtract pointer from scalar")
90-
__msg_unpriv("R1 has pointer with unsupported alu operation")
99+
__failure_unpriv
91100
__naked void deducing_bounds_from_const_6(void)
92101
{
93102
asm volatile (" \
94103
r0 = 0; \
95104
if r0 s>= 0 goto l0_%=; \
96105
exit; \
97-
l0_%=: r0 -= r1; \
106+
l0_%=: /* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
107+
r0 -= r1; \
98108
exit; \
99109
" ::: __clobber_all);
100110
}
101111

102112
SEC("socket")
103113
__description("check deducing bounds from const, 7")
104114
__failure __msg("dereference of modified ctx ptr")
105-
__msg_unpriv("R1 has pointer with unsupported alu operation")
115+
__failure_unpriv
106116
__flag(BPF_F_ANY_ALIGNMENT)
107117
__naked void deducing_bounds_from_const_7(void)
108118
{
109119
asm volatile (" \
110120
r0 = %[__imm_0]; \
111121
if r0 s>= 0 goto l0_%=; \
112-
l0_%=: r1 -= r0; \
122+
l0_%=: /* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
123+
r1 -= r0; \
113124
r0 = *(u32*)(r1 + %[__sk_buff_mark]); \
114125
exit; \
115126
" :
@@ -121,13 +132,14 @@ l0_%=: r1 -= r0; \
121132
SEC("socket")
122133
__description("check deducing bounds from const, 8")
123134
__failure __msg("negative offset ctx ptr R1 off=-1 disallowed")
124-
__msg_unpriv("R1 has pointer with unsupported alu operation")
135+
__failure_unpriv
125136
__flag(BPF_F_ANY_ALIGNMENT)
126137
__naked void deducing_bounds_from_const_8(void)
127138
{
128139
asm volatile (" \
129140
r0 = %[__imm_0]; \
130141
if r0 s>= 0 goto l0_%=; \
142+
/* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
131143
r1 += r0; \
132144
l0_%=: r0 = *(u32*)(r1 + %[__sk_buff_mark]); \
133145
exit; \
@@ -140,13 +152,14 @@ l0_%=: r0 = *(u32*)(r1 + %[__sk_buff_mark]); \
140152
SEC("socket")
141153
__description("check deducing bounds from const, 9")
142154
__failure __msg("R0 tried to subtract pointer from scalar")
143-
__msg_unpriv("R1 has pointer with unsupported alu operation")
155+
__failure_unpriv
144156
__naked void deducing_bounds_from_const_9(void)
145157
{
146158
asm volatile (" \
147159
r0 = 0; \
148160
if r0 s>= 0 goto l0_%=; \
149-
l0_%=: r0 -= r1; \
161+
l0_%=: /* unpriv: nospec (inserted to prevent `R1 has pointer with unsupported alu operation`) */\
162+
r0 -= r1; \
150163
exit; \
151164
" ::: __clobber_all);
152165
}

0 commit comments

Comments
 (0)