Skip to content

Commit e71ad5a

Browse files
luisgerhorstKernel Patches Daemon
authored and
Kernel Patches Daemon
committed
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 -ENOMEM directly now. Previously, sanitize_err() returned -EACCES for this case but we change it to -ENOMEM because doing so prevents do_check() from falling back to a nospec if we are on a speculative path. This would not be a serious issue (the verifier would probably run into the -ENOMEM again shortly on the next non-speculative path and still abort verification), but -ENOMEM is more fitting here anyway. An alternative would be -EFAULT, which is also returned for some of the other cases where push_stack() fails, but this is more frequently used for verifier-internal bugs. Signed-off-by: Luis Gerhorst <[email protected]> Acked-by: Henriette Herzog <[email protected]> Cc: Maximilian Ott <[email protected]> Cc: Milan Stephan <[email protected]>
1 parent 468b483 commit e71ad5a

File tree

5 files changed

+156
-96
lines changed

5 files changed

+156
-96
lines changed

kernel/bpf/verifier.c

+26-59
Original file line numberDiff line numberDiff line change
@@ -13967,14 +13967,6 @@ static bool check_reg_sane_offset(struct bpf_verifier_env *env,
1396713967
return true;
1396813968
}
1396913969

13970-
enum {
13971-
REASON_BOUNDS = -1,
13972-
REASON_TYPE = -2,
13973-
REASON_PATHS = -3,
13974-
REASON_LIMIT = -4,
13975-
REASON_STACK = -5,
13976-
};
13977-
1397813970
static int retrieve_ptr_limit(const struct bpf_reg_state *ptr_reg,
1397913971
u32 *alu_limit, bool mask_to_left)
1398013972
{
@@ -13997,11 +13989,13 @@ static int retrieve_ptr_limit(const struct bpf_reg_state *ptr_reg,
1399713989
ptr_reg->umax_value) + ptr_reg->off;
1399813990
break;
1399913991
default:
14000-
return REASON_TYPE;
13992+
/* Register has pointer with unsupported alu operation. */
13993+
return -EOPNOTSUPP;
1400113994
}
1400213995

13996+
/* Register tried access beyond pointer bounds. */
1400313997
if (ptr_limit >= max)
14004-
return REASON_LIMIT;
13998+
return -EOPNOTSUPP;
1400513999
*alu_limit = ptr_limit;
1400614000
return 0;
1400714001
}
@@ -14022,8 +14016,12 @@ static int update_alu_sanitation_state(struct bpf_insn_aux_data *aux,
1402214016
*/
1402314017
if (aux->alu_state &&
1402414018
(aux->alu_state != alu_state ||
14025-
aux->alu_limit != alu_limit))
14026-
return REASON_PATHS;
14019+
aux->alu_limit != alu_limit)) {
14020+
/* Tried to perform alu op from different maps, paths or scalars */
14021+
aux->nospec = true;
14022+
aux->alu_state = 0;
14023+
return 0;
14024+
}
1402714025

1402814026
/* Corresponding fixup done in do_misc_fixups(). */
1402914027
aux->alu_state = alu_state;
@@ -14104,16 +14102,24 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
1410414102

1410514103
if (!commit_window) {
1410614104
if (!tnum_is_const(off_reg->var_off) &&
14107-
(off_reg->smin_value < 0) != (off_reg->smax_value < 0))
14108-
return REASON_BOUNDS;
14105+
(off_reg->smin_value < 0) != (off_reg->smax_value < 0)) {
14106+
/* Register has unknown scalar with mixed signed bounds. */
14107+
aux->nospec = true;
14108+
aux->alu_state = 0;
14109+
return 0;
14110+
}
1410914111

1411014112
info->mask_to_left = (opcode == BPF_ADD && off_is_neg) ||
1411114113
(opcode == BPF_SUB && !off_is_neg);
1411214114
}
1411314115

1411414116
err = retrieve_ptr_limit(ptr_reg, &alu_limit, info->mask_to_left);
14115-
if (err < 0)
14116-
return err;
14117+
if (err) {
14118+
WARN_ON_ONCE(err != -EOPNOTSUPP);
14119+
aux->nospec = true;
14120+
aux->alu_state = 0;
14121+
return 0;
14122+
}
1411714123

1411814124
if (commit_window) {
1411914125
/* In commit phase we narrow the masking window based on
@@ -14166,7 +14172,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
1416614172
env->insn_idx);
1416714173
if (!ptr_is_dst_reg && ret)
1416814174
*dst_reg = tmp;
14169-
return !ret ? REASON_STACK : 0;
14175+
return !ret ? -ENOMEM : 0;
1417014176
}
1417114177

1417214178
static void sanitize_mark_insn_seen(struct bpf_verifier_env *env)
@@ -14182,45 +14188,6 @@ static void sanitize_mark_insn_seen(struct bpf_verifier_env *env)
1418214188
env->insn_aux_data[env->insn_idx].seen = env->pass_cnt;
1418314189
}
1418414190

14185-
static int sanitize_err(struct bpf_verifier_env *env,
14186-
const struct bpf_insn *insn, int reason,
14187-
const struct bpf_reg_state *off_reg,
14188-
const struct bpf_reg_state *dst_reg)
14189-
{
14190-
static const char *err = "pointer arithmetic with it prohibited for !root";
14191-
const char *op = BPF_OP(insn->code) == BPF_ADD ? "add" : "sub";
14192-
u32 dst = insn->dst_reg, src = insn->src_reg;
14193-
14194-
switch (reason) {
14195-
case REASON_BOUNDS:
14196-
verbose(env, "R%d has unknown scalar with mixed signed bounds, %s\n",
14197-
off_reg == dst_reg ? dst : src, err);
14198-
break;
14199-
case REASON_TYPE:
14200-
verbose(env, "R%d has pointer with unsupported alu operation, %s\n",
14201-
off_reg == dst_reg ? src : dst, err);
14202-
break;
14203-
case REASON_PATHS:
14204-
verbose(env, "R%d tried to %s from different maps, paths or scalars, %s\n",
14205-
dst, op, err);
14206-
break;
14207-
case REASON_LIMIT:
14208-
verbose(env, "R%d tried to %s beyond pointer bounds, %s\n",
14209-
dst, op, err);
14210-
break;
14211-
case REASON_STACK:
14212-
verbose(env, "R%d could not be pushed for speculative verification, %s\n",
14213-
dst, err);
14214-
break;
14215-
default:
14216-
verbose(env, "verifier internal error: unknown reason (%d)\n",
14217-
reason);
14218-
break;
14219-
}
14220-
14221-
return -EACCES;
14222-
}
14223-
1422414191
/* check that stack access falls within stack limits and that 'reg' doesn't
1422514192
* have a variable offset.
1422614193
*
@@ -14386,7 +14353,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
1438614353
ret = sanitize_ptr_alu(env, insn, ptr_reg, off_reg, dst_reg,
1438714354
&info, false);
1438814355
if (ret < 0)
14389-
return sanitize_err(env, insn, ret, off_reg, dst_reg);
14356+
return ret;
1439014357
}
1439114358

1439214359
switch (opcode) {
@@ -14514,7 +14481,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
1451414481
ret = sanitize_ptr_alu(env, insn, dst_reg, off_reg, dst_reg,
1451514482
&info, true);
1451614483
if (ret < 0)
14517-
return sanitize_err(env, insn, ret, off_reg, dst_reg);
14484+
return ret;
1451814485
}
1451914486

1452014487
return 0;
@@ -15108,7 +15075,7 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
1510815075
if (sanitize_needed(opcode)) {
1510915076
ret = sanitize_val_alu(env, insn);
1511015077
if (ret < 0)
15111-
return sanitize_err(env, insn, ret, NULL, NULL);
15078+
return ret;
1511215079
}
1511315080

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

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

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

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

+29-16
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
}

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

+16-4
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,17 @@ __naked void ptr_read_ops_field_accepted(void)
110110

111111
SEC("socket")
112112
__description("bpf_map_ptr: r = 0, map_ptr = map_ptr + r")
113-
__success __failure_unpriv
114-
__msg_unpriv("R1 has pointer with unsupported alu operation")
113+
__success __success_unpriv
115114
__retval(0)
115+
#ifdef SPEC_V1
116+
__xlated_unpriv("r1 = 0x") /* r0 = %[map_hash_16b] ll */
117+
/* This nospec is not really needed here, because there is no bypassable
118+
* branch/store that could lead to r0 not containing 0 when `r1 += r0` executes.
119+
* We still keep it like this to not complicate the verifier.
120+
*/
121+
__xlated_unpriv("nospec") /* inserted to prevent `R1 has pointer with unsupported alu operation` */
122+
__xlated_unpriv("r1 += r0")
123+
#endif
116124
__naked void map_ptr_map_ptr_r(void)
117125
{
118126
asm volatile (" \
@@ -134,9 +142,13 @@ __naked void map_ptr_map_ptr_r(void)
134142

135143
SEC("socket")
136144
__description("bpf_map_ptr: r = 0, r = r + map_ptr")
137-
__success __failure_unpriv
138-
__msg_unpriv("R0 has pointer with unsupported alu operation")
145+
__success __success_unpriv
139146
__retval(0)
147+
#ifdef SPEC_V1
148+
__xlated_unpriv("r0 = 0x") /* r0 = %[map_hash_16b] ll */
149+
__xlated_unpriv("nospec") /* inserted to prevent `R0 has pointer with unsupported alu operation` */
150+
__xlated_unpriv("r1 += r0")
151+
#endif
140152
__naked void _0_r_r_map_ptr(void)
141153
{
142154
asm volatile (" \

0 commit comments

Comments
 (0)