* [PATCH net-next 0/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics
@ 2017-11-12 16:03 Yonghong Song
2017-11-12 16:03 ` [PATCH net-next 1/3 " Yonghong Song
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Yonghong Song @ 2017-11-12 16:03 UTC (permalink / raw)
To: ast, daniel, netdev; +Cc: kernel-team
This patch set intends to change verifier ARG_CONST_SIZE_OR_ZERO
semantics so that simpler bpf programs can be written with verifier
acceptance. Patch #1 comment provided the detailed examples and
the patch itself implements the new semantics. Patch #2
changes bpf_probe_read helper arg2 type from
ARG_CONST_SIZE to ARG_CONST_SIZE_OR_ZERO. Patch #3 fixed a few
test cases and added some for better coverage.
Changelog:
v1 -> v2:
Fix typo in commit message pointed by Sergei Shtylyov
Yonghong Song (3):
bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics
bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO
bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics
change
kernel/bpf/verifier.c | 40 +++++----
kernel/trace/bpf_trace.c | 8 +-
tools/testing/selftests/bpf/test_verifier.c | 131 ++++++++++++++++++++++++----
3 files changed, 142 insertions(+), 37 deletions(-)
--
2.9.5
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next 1/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics
2017-11-12 16:03 [PATCH net-next 0/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics Yonghong Song
@ 2017-11-12 16:03 ` Yonghong Song
2017-11-12 19:20 ` Daniel Borkmann
2017-11-12 16:04 ` [PATCH net-next 2/3 v2] bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO Yonghong Song
2017-11-12 16:04 ` [PATCH net-next 3/3 v2] bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics change Yonghong Song
2 siblings, 1 reply; 7+ messages in thread
From: Yonghong Song @ 2017-11-12 16:03 UTC (permalink / raw)
To: ast, daniel, netdev; +Cc: kernel-team
For helpers, the argument type ARG_CONST_SIZE_OR_ZERO permits the
access size to be 0 when accessing the previous argument (arg).
Right now, it requires the arg needs to be NULL when size passed
is 0 or could be 0. It also requires a non-NULL arg when the size
is proved to be non-0.
This patch changes verifier ARG_CONST_SIZE_OR_ZERO behavior
such that for size-0 or possible size-0, it is not required
the arg equal to NULL.
There are a couple of reasons for this semantics change, and
all of them intends to simplify user bpf programs which
may improve user experience and/or increase chances of
verifier acceptance. Together with the next patch which
changes bpf_probe_read arg2 type from ARG_CONST_SIZE to
ARG_CONST_SIZE_OR_ZERO, the following two examples, which
fail the verifier currently, are able to get verifier acceptance.
Example 1:
==========
unsigned long len = pend - pstart;
len = len > MAX_PAYLOAD_LEN ? MAX_PAYLOAD_LEN : len;
len &= MAX_PAYLOAD_LEN;
bpf_probe_read(data->payload, len, pstart);
It does not have test for "len > 0" and it failed the verifier.
Users may not be aware that they have to add this test.
Converting the bpf_probe_read helper to have
ARG_CONST_SIZE_OR_ZERO helps the above code get
verifier acceptance.
Example 2:
==========
Here is one example where llvm "messed up" the code and
the verifier fails.
......
unsigned long len = pend - pstart;
if (len > 0 && len <= MAX_PAYLOAD_LEN)
bpf_probe_read(data->payload, len, pstart);
......
The compiler generates the following code and verifier fails:
......
39: (79) r2 = *(u64 *)(r10 -16)
40: (1f) r2 -= r8
41: (bf) r1 = r2
42: (07) r1 += -1
43: (25) if r1 > 0xffe goto pc+3
R0=inv(id=0) R1=inv(id=0,umax_value=4094,var_off=(0x0; 0xfff))
R2=inv(id=0) R6=map_value(id=0,off=0,ks=4,vs=4095,imm=0) R7=inv(id=0)
R8=inv(id=0) R9=inv0 R10=fp0
44: (bf) r1 = r6
45: (bf) r3 = r8
46: (85) call bpf_probe_read#45
R2 min value is negative, either use unsigned or 'var &= const'
......
The compiler optimization is correct. If r1 = 0,
r1 - 1 = 0xffffffffffffffff > 0xffe. If r1 != 0, r1 - 1 will not wrap.
r1 > 0xffe at insn #43 can actually capture
both "r1 > 0" and "len <= MAX_PAYLOAD_LEN".
This however causes an issue in verifier as the value range of arg2
"r2" does not properly get refined and lead to verification failure.
Relaxing bpf_prog_read arg2 from ARG_CONST_SIZE to ARG_CONST_SIZE_OR_ZERO
allows the following simplied code:
unsigned long len = pend - pstart;
if (len <= MAX_PAYLOAD_LEN)
bpf_probe_read(data->payload, len, pstart);
The llvm compiler will generate less complex code and the
verifier is able to verify that the program is okay.
Signed-off-by: Yonghong Song <yhs@fb.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
---
kernel/bpf/verifier.c | 40 ++++++++++++++++++++++++----------------
1 file changed, 24 insertions(+), 16 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 4a942e2..dd54d20 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -799,12 +799,13 @@ static int check_stack_read(struct bpf_verifier_env *env,
/* check read/write into map element returned by bpf_map_lookup_elem() */
static int __check_map_access(struct bpf_verifier_env *env, u32 regno, int off,
- int size)
+ int size, bool zero_size_allowed)
{
struct bpf_reg_state *regs = cur_regs(env);
struct bpf_map *map = regs[regno].map_ptr;
- if (off < 0 || size <= 0 || off + size > map->value_size) {
+ if (off < 0 || size < 0 || (size == 0 && !zero_size_allowed) ||
+ off + size > map->value_size) {
verbose(env, "invalid access to map value, value_size=%d off=%d size=%d\n",
map->value_size, off, size);
return -EACCES;
@@ -814,7 +815,7 @@ static int __check_map_access(struct bpf_verifier_env *env, u32 regno, int off,
/* check read/write into a map element with possible variable offset */
static int check_map_access(struct bpf_verifier_env *env, u32 regno,
- int off, int size)
+ int off, int size, bool zero_size_allowed)
{
struct bpf_verifier_state *state = env->cur_state;
struct bpf_reg_state *reg = &state->regs[regno];
@@ -837,7 +838,8 @@ static int check_map_access(struct bpf_verifier_env *env, u32 regno,
regno);
return -EACCES;
}
- err = __check_map_access(env, regno, reg->smin_value + off, size);
+ err = __check_map_access(env, regno, reg->smin_value + off, size,
+ zero_size_allowed);
if (err) {
verbose(env, "R%d min value is outside of the array range\n",
regno);
@@ -853,7 +855,8 @@ static int check_map_access(struct bpf_verifier_env *env, u32 regno,
regno);
return -EACCES;
}
- err = __check_map_access(env, regno, reg->umax_value + off, size);
+ err = __check_map_access(env, regno, reg->umax_value + off, size,
+ zero_size_allowed);
if (err)
verbose(env, "R%d max value is outside of the array range\n",
regno);
@@ -889,12 +892,13 @@ static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
}
static int __check_packet_access(struct bpf_verifier_env *env, u32 regno,
- int off, int size)
+ int off, int size, bool zero_size_allowed)
{
struct bpf_reg_state *regs = cur_regs(env);
struct bpf_reg_state *reg = ®s[regno];
- if (off < 0 || size <= 0 || (u64)off + size > reg->range) {
+ if (off < 0 || size < 0 || (size == 0 && !zero_size_allowed) ||
+ (u64)off + size > reg->range) {
verbose(env, "invalid access to packet, off=%d size=%d, R%d(id=%d,off=%d,r=%d)\n",
off, size, regno, reg->id, reg->off, reg->range);
return -EACCES;
@@ -903,7 +907,7 @@ static int __check_packet_access(struct bpf_verifier_env *env, u32 regno,
}
static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off,
- int size)
+ int size, bool zero_size_allowed)
{
struct bpf_reg_state *regs = cur_regs(env);
struct bpf_reg_state *reg = ®s[regno];
@@ -922,7 +926,7 @@ static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off,
regno);
return -EACCES;
}
- err = __check_packet_access(env, regno, off, size);
+ err = __check_packet_access(env, regno, off, size, zero_size_allowed);
if (err) {
verbose(env, "R%d offset is outside of the packet\n", regno);
return err;
@@ -1097,7 +1101,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
return -EACCES;
}
- err = check_map_access(env, regno, off, size);
+ err = check_map_access(env, regno, off, size, false);
if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
@@ -1184,7 +1188,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
value_regno);
return -EACCES;
}
- err = check_packet_access(env, regno, off, size);
+ err = check_packet_access(env, regno, off, size, false);
if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else {
@@ -1281,7 +1285,7 @@ static int check_stack_boundary(struct bpf_verifier_env *env, int regno,
}
off = regs[regno].off + regs[regno].var_off.value;
if (off >= 0 || off < -MAX_BPF_STACK || off + access_size > 0 ||
- access_size <= 0) {
+ access_size < 0 || (access_size == 0 && !zero_size_allowed)) {
verbose(env, "invalid stack type R%d off=%d access_size=%d\n",
regno, off, access_size);
return -EACCES;
@@ -1319,9 +1323,11 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
switch (reg->type) {
case PTR_TO_PACKET:
case PTR_TO_PACKET_META:
- return check_packet_access(env, regno, reg->off, access_size);
+ return check_packet_access(env, regno, reg->off, access_size,
+ zero_size_allowed);
case PTR_TO_MAP_VALUE:
- return check_map_access(env, regno, reg->off, access_size);
+ return check_map_access(env, regno, reg->off, access_size,
+ zero_size_allowed);
default: /* scalar_value|ptr_to_stack or invalid ptr */
return check_stack_boundary(env, regno, access_size,
zero_size_allowed, meta);
@@ -1415,7 +1421,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
}
if (type_is_pkt_pointer(type))
err = check_packet_access(env, regno, reg->off,
- meta->map_ptr->key_size);
+ meta->map_ptr->key_size,
+ false);
else
err = check_stack_boundary(env, regno,
meta->map_ptr->key_size,
@@ -1431,7 +1438,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
}
if (type_is_pkt_pointer(type))
err = check_packet_access(env, regno, reg->off,
- meta->map_ptr->value_size);
+ meta->map_ptr->value_size,
+ false);
else
err = check_stack_boundary(env, regno,
meta->map_ptr->value_size,
--
2.9.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next 2/3 v2] bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO
2017-11-12 16:03 [PATCH net-next 0/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics Yonghong Song
2017-11-12 16:03 ` [PATCH net-next 1/3 " Yonghong Song
@ 2017-11-12 16:04 ` Yonghong Song
2017-11-12 19:20 ` Daniel Borkmann
2017-11-12 16:04 ` [PATCH net-next 3/3 v2] bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics change Yonghong Song
2 siblings, 1 reply; 7+ messages in thread
From: Yonghong Song @ 2017-11-12 16:04 UTC (permalink / raw)
To: ast, daniel, netdev; +Cc: kernel-team
The helper bpf_probe_read arg2 type is changed
from ARG_CONST_SIZE to ARG_CONST_SIZE_OR_ZERO to permit
size-0 buffer. Together with newer ARG_CONST_SIZE_OR_ZERO
semantics which allows non-NULL buffer with size 0,
this allows simpler bpf programs with verifier acceptance.
The previous commit which changes ARG_CONST_SIZE_OR_ZERO semantics
has details on examples.
Signed-off-by: Yonghong Song <yhs@fb.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
---
kernel/trace/bpf_trace.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 506efe6..a5580c6 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -78,12 +78,16 @@ EXPORT_SYMBOL_GPL(trace_call_bpf);
BPF_CALL_3(bpf_probe_read, void *, dst, u32, size, const void *, unsafe_ptr)
{
- int ret;
+ int ret = 0;
+
+ if (unlikely(size == 0))
+ goto out;
ret = probe_kernel_read(dst, unsafe_ptr, size);
if (unlikely(ret < 0))
memset(dst, 0, size);
+ out:
return ret;
}
@@ -92,7 +96,7 @@ static const struct bpf_func_proto bpf_probe_read_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
- .arg2_type = ARG_CONST_SIZE,
+ .arg2_type = ARG_CONST_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
};
--
2.9.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next 3/3 v2] bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics change
2017-11-12 16:03 [PATCH net-next 0/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics Yonghong Song
2017-11-12 16:03 ` [PATCH net-next 1/3 " Yonghong Song
2017-11-12 16:04 ` [PATCH net-next 2/3 v2] bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO Yonghong Song
@ 2017-11-12 16:04 ` Yonghong Song
2017-11-12 19:20 ` Daniel Borkmann
2 siblings, 1 reply; 7+ messages in thread
From: Yonghong Song @ 2017-11-12 16:04 UTC (permalink / raw)
To: ast, daniel, netdev; +Cc: kernel-team
Fix a few test cases to allow non-NULL map/packet/stack pointer
with size = 0. Change a few tests using bpf_probe_read to use
bpf_probe_write_user so ARG_CONST_SIZE arg can still be properly
tested. One existing test case already covers size = 0 with non-NULL
packet pointer, so add additional tests so all cases of
size = 0 and 0 <= size <= legal_upper_bound with non-NULL
map/packet/stack pointer are covered.
Signed-off-by: Yonghong Song <yhs@fb.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
---
tools/testing/selftests/bpf/test_verifier.c | 131 ++++++++++++++++++++++++----
1 file changed, 112 insertions(+), 19 deletions(-)
diff --git a/tools/testing/selftests/bpf/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c
index bb3c4ad..bf092b8 100644
--- a/tools/testing/selftests/bpf/test_verifier.c
+++ b/tools/testing/selftests/bpf/test_verifier.c
@@ -3579,7 +3579,7 @@ static struct bpf_test tests[] = {
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
{
- "helper access to packet: test19, cls helper fail range zero",
+ "helper access to packet: test19, cls helper range zero",
.insns = {
BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
offsetof(struct __sk_buff, data)),
@@ -3599,8 +3599,7 @@ static struct bpf_test tests[] = {
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
},
- .result = REJECT,
- .errstr = "invalid access to packet",
+ .result = ACCEPT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
{
@@ -4379,10 +4378,10 @@ static struct bpf_test tests[] = {
BPF_LD_MAP_FD(BPF_REG_1, 0),
BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 4),
- BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
- BPF_MOV64_IMM(BPF_REG_2, 0),
+ BPF_MOV64_IMM(BPF_REG_1, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_0),
BPF_MOV64_IMM(BPF_REG_3, 0),
- BPF_EMIT_CALL(BPF_FUNC_probe_read),
+ BPF_EMIT_CALL(BPF_FUNC_probe_write_user),
BPF_EXIT_INSN(),
},
.fixup_map2 = { 3 },
@@ -4486,9 +4485,10 @@ static struct bpf_test tests[] = {
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1,
offsetof(struct test_val, foo)),
- BPF_MOV64_IMM(BPF_REG_2, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_1),
+ BPF_MOV64_IMM(BPF_REG_1, 0),
BPF_MOV64_IMM(BPF_REG_3, 0),
- BPF_EMIT_CALL(BPF_FUNC_probe_read),
+ BPF_EMIT_CALL(BPF_FUNC_probe_write_user),
BPF_EXIT_INSN(),
},
.fixup_map2 = { 3 },
@@ -4622,13 +4622,14 @@ static struct bpf_test tests[] = {
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_MOV64_IMM(BPF_REG_3, 0),
BPF_ALU64_REG(BPF_ADD, BPF_REG_1, BPF_REG_3),
- BPF_MOV64_IMM(BPF_REG_2, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_1),
+ BPF_MOV64_IMM(BPF_REG_1, 0),
BPF_MOV64_IMM(BPF_REG_3, 0),
- BPF_EMIT_CALL(BPF_FUNC_probe_read),
+ BPF_EMIT_CALL(BPF_FUNC_probe_write_user),
BPF_EXIT_INSN(),
},
.fixup_map2 = { 3 },
- .errstr = "R1 min value is outside of the array range",
+ .errstr = "R2 min value is outside of the array range",
.result = REJECT,
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
},
@@ -4765,13 +4766,14 @@ static struct bpf_test tests[] = {
BPF_JMP_IMM(BPF_JGT, BPF_REG_3,
offsetof(struct test_val, foo), 4),
BPF_ALU64_REG(BPF_ADD, BPF_REG_1, BPF_REG_3),
- BPF_MOV64_IMM(BPF_REG_2, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_1),
+ BPF_MOV64_IMM(BPF_REG_1, 0),
BPF_MOV64_IMM(BPF_REG_3, 0),
- BPF_EMIT_CALL(BPF_FUNC_probe_read),
+ BPF_EMIT_CALL(BPF_FUNC_probe_write_user),
BPF_EXIT_INSN(),
},
.fixup_map2 = { 3 },
- .errstr = "R1 min value is outside of the array range",
+ .errstr = "R2 min value is outside of the array range",
.result = REJECT,
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
},
@@ -5350,7 +5352,7 @@ static struct bpf_test tests[] = {
BPF_EMIT_CALL(BPF_FUNC_probe_read),
BPF_EXIT_INSN(),
},
- .errstr = "invalid stack type R1 off=-64 access_size=0",
+ .errstr = "invalid indirect read from stack off -64+0 size 64",
.result = REJECT,
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
},
@@ -5505,7 +5507,7 @@ static struct bpf_test tests[] = {
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
},
- .errstr = "invalid stack type R1 off=-64 access_size=0",
+ .errstr = "invalid indirect read from stack off -64+0 size 64",
.result = REJECT,
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
},
@@ -5668,7 +5670,7 @@ static struct bpf_test tests[] = {
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
{
- "helper access to variable memory: size = 0 not allowed on != NULL",
+ "helper access to variable memory: size = 0 allowed on != NULL stack pointer",
.insns = {
BPF_MOV64_REG(BPF_REG_1, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -8),
@@ -5681,8 +5683,99 @@ static struct bpf_test tests[] = {
BPF_EMIT_CALL(BPF_FUNC_csum_diff),
BPF_EXIT_INSN(),
},
- .errstr = "invalid stack type R1 off=-8 access_size=0",
- .result = REJECT,
+ .result = ACCEPT,
+ .prog_type = BPF_PROG_TYPE_SCHED_CLS,
+ },
+ {
+ "helper access to variable memory: size = 0 allowed on != NULL map pointer",
+ .insns = {
+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
+ BPF_LD_MAP_FD(BPF_REG_1, 0),
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
+ BPF_FUNC_map_lookup_elem),
+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 6),
+ BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
+ BPF_MOV64_IMM(BPF_REG_2, 0),
+ BPF_MOV64_IMM(BPF_REG_3, 0),
+ BPF_MOV64_IMM(BPF_REG_4, 0),
+ BPF_MOV64_IMM(BPF_REG_5, 0),
+ BPF_EMIT_CALL(BPF_FUNC_csum_diff),
+ BPF_EXIT_INSN(),
+ },
+ .fixup_map1 = { 3 },
+ .result = ACCEPT,
+ .prog_type = BPF_PROG_TYPE_SCHED_CLS,
+ },
+ {
+ "helper access to variable memory: size possible = 0 allowed on != NULL stack pointer",
+ .insns = {
+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
+ BPF_LD_MAP_FD(BPF_REG_1, 0),
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
+ BPF_FUNC_map_lookup_elem),
+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 9),
+ BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, 0),
+ BPF_JMP_IMM(BPF_JGT, BPF_REG_2, 8, 7),
+ BPF_MOV64_REG(BPF_REG_1, BPF_REG_10),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -8),
+ BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_2, 0),
+ BPF_MOV64_IMM(BPF_REG_3, 0),
+ BPF_MOV64_IMM(BPF_REG_4, 0),
+ BPF_MOV64_IMM(BPF_REG_5, 0),
+ BPF_EMIT_CALL(BPF_FUNC_csum_diff),
+ BPF_EXIT_INSN(),
+ },
+ .fixup_map1 = { 3 },
+ .result = ACCEPT,
+ .prog_type = BPF_PROG_TYPE_SCHED_CLS,
+ },
+ {
+ "helper access to variable memory: size possible = 0 allowed on != NULL map pointer",
+ .insns = {
+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
+ BPF_LD_MAP_FD(BPF_REG_1, 0),
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
+ BPF_FUNC_map_lookup_elem),
+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 7),
+ BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
+ BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, 0),
+ BPF_JMP_IMM(BPF_JGT, BPF_REG_2, 8, 4),
+ BPF_MOV64_IMM(BPF_REG_3, 0),
+ BPF_MOV64_IMM(BPF_REG_4, 0),
+ BPF_MOV64_IMM(BPF_REG_5, 0),
+ BPF_EMIT_CALL(BPF_FUNC_csum_diff),
+ BPF_EXIT_INSN(),
+ },
+ .fixup_map1 = { 3 },
+ .result = ACCEPT,
+ .prog_type = BPF_PROG_TYPE_SCHED_CLS,
+ },
+ {
+ "helper access to variable memory: size possible = 0 allowed on != NULL packet pointer",
+ .insns = {
+ BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
+ offsetof(struct __sk_buff, data)),
+ BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1,
+ offsetof(struct __sk_buff, data_end)),
+ BPF_MOV64_REG(BPF_REG_0, BPF_REG_6),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 8),
+ BPF_JMP_REG(BPF_JGT, BPF_REG_0, BPF_REG_3, 7),
+ BPF_MOV64_REG(BPF_REG_1, BPF_REG_6),
+ BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6, 0),
+ BPF_JMP_IMM(BPF_JGT, BPF_REG_2, 8, 4),
+ BPF_MOV64_IMM(BPF_REG_3, 0),
+ BPF_MOV64_IMM(BPF_REG_4, 0),
+ BPF_MOV64_IMM(BPF_REG_5, 0),
+ BPF_EMIT_CALL(BPF_FUNC_csum_diff),
+ BPF_EXIT_INSN(),
+ },
+ .result = ACCEPT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
{
--
2.9.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net-next 1/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics
2017-11-12 16:03 ` [PATCH net-next 1/3 " Yonghong Song
@ 2017-11-12 19:20 ` Daniel Borkmann
0 siblings, 0 replies; 7+ messages in thread
From: Daniel Borkmann @ 2017-11-12 19:20 UTC (permalink / raw)
To: Yonghong Song, ast, netdev; +Cc: kernel-team
On 11/12/2017 05:03 PM, Yonghong Song wrote:
> For helpers, the argument type ARG_CONST_SIZE_OR_ZERO permits the
> access size to be 0 when accessing the previous argument (arg).
> Right now, it requires the arg needs to be NULL when size passed
> is 0 or could be 0. It also requires a non-NULL arg when the size
> is proved to be non-0.
>
> This patch changes verifier ARG_CONST_SIZE_OR_ZERO behavior
> such that for size-0 or possible size-0, it is not required
> the arg equal to NULL.
>
> There are a couple of reasons for this semantics change, and
> all of them intends to simplify user bpf programs which
> may improve user experience and/or increase chances of
> verifier acceptance. Together with the next patch which
> changes bpf_probe_read arg2 type from ARG_CONST_SIZE to
> ARG_CONST_SIZE_OR_ZERO, the following two examples, which
> fail the verifier currently, are able to get verifier acceptance.
>
> Example 1:
Looks like patchwork messes up your patch description, see:
http://patchwork.ozlabs.org/patch/837213/
I think it matches the '===' line with start of the patch. Would be
good if this can be fixed.
> ==========
> unsigned long len = pend - pstart;
> len = len > MAX_PAYLOAD_LEN ? MAX_PAYLOAD_LEN : len;
> len &= MAX_PAYLOAD_LEN;
> bpf_probe_read(data->payload, len, pstart);
>
> It does not have test for "len > 0" and it failed the verifier.
> Users may not be aware that they have to add this test.
> Converting the bpf_probe_read helper to have
> ARG_CONST_SIZE_OR_ZERO helps the above code get
> verifier acceptance.
>
> Example 2:
> ==========
> Here is one example where llvm "messed up" the code and
> the verifier fails.
>
> ......
> unsigned long len = pend - pstart;
> if (len > 0 && len <= MAX_PAYLOAD_LEN)
> bpf_probe_read(data->payload, len, pstart);
> ......
>
> The compiler generates the following code and verifier fails:
> ......
> 39: (79) r2 = *(u64 *)(r10 -16)
> 40: (1f) r2 -= r8
> 41: (bf) r1 = r2
> 42: (07) r1 += -1
> 43: (25) if r1 > 0xffe goto pc+3
> R0=inv(id=0) R1=inv(id=0,umax_value=4094,var_off=(0x0; 0xfff))
> R2=inv(id=0) R6=map_value(id=0,off=0,ks=4,vs=4095,imm=0) R7=inv(id=0)
> R8=inv(id=0) R9=inv0 R10=fp0
> 44: (bf) r1 = r6
> 45: (bf) r3 = r8
> 46: (85) call bpf_probe_read#45
> R2 min value is negative, either use unsigned or 'var &= const'
> ......
>
> The compiler optimization is correct. If r1 = 0,
> r1 - 1 = 0xffffffffffffffff > 0xffe. If r1 != 0, r1 - 1 will not wrap.
> r1 > 0xffe at insn #43 can actually capture
> both "r1 > 0" and "len <= MAX_PAYLOAD_LEN".
> This however causes an issue in verifier as the value range of arg2
> "r2" does not properly get refined and lead to verification failure.
>
> Relaxing bpf_prog_read arg2 from ARG_CONST_SIZE to ARG_CONST_SIZE_OR_ZERO
> allows the following simplied code:
> unsigned long len = pend - pstart;
> if (len <= MAX_PAYLOAD_LEN)
> bpf_probe_read(data->payload, len, pstart);
>
> The llvm compiler will generate less complex code and the
> verifier is able to verify that the program is okay.
>
> Signed-off-by: Yonghong Song <yhs@fb.com>
> Acked-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next 2/3 v2] bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO
2017-11-12 16:04 ` [PATCH net-next 2/3 v2] bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO Yonghong Song
@ 2017-11-12 19:20 ` Daniel Borkmann
0 siblings, 0 replies; 7+ messages in thread
From: Daniel Borkmann @ 2017-11-12 19:20 UTC (permalink / raw)
To: Yonghong Song, ast, netdev; +Cc: kernel-team
On 11/12/2017 05:04 PM, Yonghong Song wrote:
> The helper bpf_probe_read arg2 type is changed
> from ARG_CONST_SIZE to ARG_CONST_SIZE_OR_ZERO to permit
> size-0 buffer. Together with newer ARG_CONST_SIZE_OR_ZERO
> semantics which allows non-NULL buffer with size 0,
> this allows simpler bpf programs with verifier acceptance.
> The previous commit which changes ARG_CONST_SIZE_OR_ZERO semantics
> has details on examples.
>
> Signed-off-by: Yonghong Song <yhs@fb.com>
> Acked-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next 3/3 v2] bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics change
2017-11-12 16:04 ` [PATCH net-next 3/3 v2] bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics change Yonghong Song
@ 2017-11-12 19:20 ` Daniel Borkmann
0 siblings, 0 replies; 7+ messages in thread
From: Daniel Borkmann @ 2017-11-12 19:20 UTC (permalink / raw)
To: Yonghong Song, ast, netdev; +Cc: kernel-team
On 11/12/2017 05:04 PM, Yonghong Song wrote:
> Fix a few test cases to allow non-NULL map/packet/stack pointer
> with size = 0. Change a few tests using bpf_probe_read to use
> bpf_probe_write_user so ARG_CONST_SIZE arg can still be properly
> tested. One existing test case already covers size = 0 with non-NULL
> packet pointer, so add additional tests so all cases of
> size = 0 and 0 <= size <= legal_upper_bound with non-NULL
> map/packet/stack pointer are covered.
>
> Signed-off-by: Yonghong Song <yhs@fb.com>
> Acked-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2017-11-12 19:21 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-12 16:03 [PATCH net-next 0/3 v2] bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics Yonghong Song
2017-11-12 16:03 ` [PATCH net-next 1/3 " Yonghong Song
2017-11-12 19:20 ` Daniel Borkmann
2017-11-12 16:04 ` [PATCH net-next 2/3 v2] bpf: change helper bpf_probe_read arg2 type to ARG_CONST_SIZE_OR_ZERO Yonghong Song
2017-11-12 19:20 ` Daniel Borkmann
2017-11-12 16:04 ` [PATCH net-next 3/3 v2] bpf: fix and add test cases for ARG_CONST_SIZE_OR_ZERO semantics change Yonghong Song
2017-11-12 19:20 ` Daniel Borkmann
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.