bpf: Allow narrow loads with offset > 0
Subsystems (6)
v2 cover bpf: Allow narrow loads with offset > 0
This patch set adds support for narrow loads with offset > 0 to BPF verifier.
Patch 1 provides more details and is the main patch in the set. Patches 2 and 3 add new test cases to test_verifier and test_sock_addr selftests.
v1->v2:
- fix -Wdeclaration-after-statement warning.
Andrey Ignatov (3):
bpf: Allow narrow loads with offset > 0 selftests/bpf: Test narrow loads with off > 0 in test_verifier selftests/bpf: Test narrow loads with off > 0 for bpf_sock_addr
include/linux/filter.h | 16 +------ kernel/bpf/verifier.c | 21 +++++++-- tools/testing/selftests/bpf/test_sock_addr.c | 28 ++++++++++-- tools/testing/selftests/bpf/test_verifier.c | 48 ++++++++++++++++---- 4 files changed, 79 insertions(+), 34 deletions(-)
Signature · 1 line
2.17.1
PATCH v2 1/3 bpf: Allow narrow loads with offset > 0
Currently BPF verifier allows narrow loads for a context field only with offset zero. E.g. if there is a __u32 field then only the following loads are permitted:
* off=0, size=1 (narrow);
* off=0, size=2 (narrow);
* off=0, size=4 (full).
On the other hand LLVM can generate a load with offset different than zero that make sense from program logic point of view, but verifier doesn't accept it.
E.g. tools/testing/selftests/bpf/sendmsg4_prog.c has code:
#define DST_IP4 0xC0A801FEU /* 192.168.1.254 */ ... if ((ctx->user_ip4 >> 24) == (bpf_htonl(DST_IP4) >> 24) &&
where ctx is struct bpf_sock_addr.
Some versions of LLVM can produce the following byte code for it:
8: 71 12 07 00 00 00 00 00 r2 = *(u8 *)(r1 + 7)
9: 67 02 00 00 18 00 00 00 r2 <<= 24
10: 18 03 00 00 00 00 00 fe 00 00 00 00 00 00 00 00 r3 = 4261412864 ll
12: 5d 32 07 00 00 00 00 00 if r2 != r3 goto +7 <LBB0_6>where *(u8 *)(r1 + 7) means narrow load for ctx->user_ip4 with size=1 and offset=3 (7 - sizeof(ctx->user_family) = 3). This load is currently rejected by verifier.
Verifier code that rejects such loads is in bpf_ctx_narrow_access_ok() what means any is_valid_access implementation, that uses the function, works this way, e.g. bpf_skb_is_valid_access() for __sk_buff or sock_addr_is_valid_access() for bpf_sock_addr.
The patch makes such loads supported. Offset can be in [0; size_default) but has to be multiple of load size. E.g. for __u32 field the following loads are supported now:
* off=0, size=1 (narrow);
* off=1, size=1 (narrow);
* off=2, size=1 (narrow);
* off=3, size=1 (narrow);
* off=0, size=2 (narrow);
* off=2, size=2 (narrow);
* off=0, size=4 (full).
--- include/linux/filter.h | 16 +--------------- kernel/bpf/verifier.c | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 20 deletions(-)
include/linux/filter.h +1 -15
@@ -668,24 +668,10 @@ static inline u32 bpf_ctx_off_adjust_machine(u32 size)return size; } -static inline bool bpf_ctx_narrow_align_ok (u32 off,u32 size_access, -u32 size_default) -{ - size_default =bpf_ctx_off_adjust_machine (size_default); - size_access =bpf_ctx_off_adjust_machine (size_access); - -#ifdef __LITTLE_ENDIAN -return (off & (size_default -1 )) ==0 ; -#else -return (off & (size_default -1 )) + size_access == size_default; -#endif -} -static inline bool bpf_ctx_narrow_access_ok (u32 off,u32 size,u32 size_default) { -return bpf_ctx_narrow_align_ok (off, size, size_default) && - size <= size_default && (size & (size -1 )) ==0 ; +return size <= size_default && (size & (size -1 )) ==0 ; }#define bpf_classic_proglen (fprog) (fprog->len * sizeof(fprog->filter[0]))
kernel/bpf/verifier.c +16 -5
@@ -5718,10 +5718,10 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)int i, cnt, size, ctx_field_size, delta =0 ;const int insn_cnt = env->prog->len;struct bpf_insn insn_buf[16 ], *insn; +u32 target_size, size_default, off;struct bpf_prog *new_prog;enum bpf_access_type type;bool is_narrower_load; -u32 target_size;if (ops->gen_prologue || env->seen_direct_write) {if (!ops->gen_prologue) {
@@ -5814,9 +5814,9 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)* we will apply proper mask to the result. */ is_narrower_load = size < ctx_field_size; + size_default =bpf_ctx_off_adjust_machine (ctx_field_size); + off = insn->off;if (is_narrower_load) { -u32 size_default =bpf_ctx_off_adjust_machine (ctx_field_size); -u32 off = insn->off;u8 size_code;if (type ==BPF_WRITE ) {
@@ -5844,12 +5844,23 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env) }if (is_narrower_load && size < target_size) { -if (ctx_field_size <=4 ) +u8 shift = (off & (size_default -1 )) *8 ; + +if (ctx_field_size <=4 ) { +if (shift) + insn_buf[cnt++] =BPF_ALU32_IMM (BPF_RSH , + insn->dst_reg, + shift); insn_buf[cnt++] =BPF_ALU32_IMM (BPF_AND , insn->dst_reg, (1 << size *8 ) -1 ); -else + }else { +if (shift) + insn_buf[cnt++] =BPF_ALU64_IMM (BPF_RSH , + insn->dst_reg, + shift); insn_buf[cnt++] =BPF_ALU64_IMM (BPF_AND , insn->dst_reg, (1 << size *8 ) -1 ); + } } new_prog =bpf_patch_insn_data (env, i + delta, insn_buf, cnt);
Signature · 1 line
2.17.1
PATCH v2 2/3 selftests/bpf: Test narrow loads with off > 0 in test_verifier
In reply to Andrey IgnatovTest the following narrow loads in test_verifier for context __sk_buff:
* off=1, size=1 - ok;
* off=2, size=1 - ok;
* off=3, size=1 - ok;
* off=0, size=2 - ok;
* off=1, size=2 - fail;
* off=0, size=2 - ok;
* off=3, size=2 - fail.
--- tools/testing/selftests/bpf/test_verifier.c | 48 ++++++++++++++++----- 1 file changed, 38 insertions(+), 10 deletions(-)
tools/testing/selftests/bpf/test_verifier.c +38 -10
@@ -2026,29 +2026,27 @@ static struct bpf_test tests[] = { .result = ACCEPT, }, { - "check skb->hash byte load not permitted 1", + "check skb->hash byte load permitted 1", .insns = {BPF_MOV64_IMM (BPF_REG_0 ,0 ),BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_1 , offsetof(struct __sk_buff , hash) +1 ), BPF_EXIT_INSN(), }, - .errstr ="invalid bpf_context access" , - .result = REJECT, + .result =ACCEPT , }, { -"check skb->hash byte load not permitted 2" , +"check skb->hash byte load permitted 2" , .insns = {BPF_MOV64_IMM (BPF_REG_0 ,0 ),BPF_LDX_MEM (BPF_B ,BPF_REG_0 ,BPF_REG_1 , offsetof(struct __sk_buff , hash) +2 ), BPF_EXIT_INSN(), }, - .errstr ="invalid bpf_context access" , - .result = REJECT, + .result =ACCEPT , }, { -"check skb->hash byte load not permitted 3" , +"check skb->hash byte load permitted 3" , .insns = {BPF_MOV64_IMM (BPF_REG_0 ,0 ), #if __BYTE_ORDER == __LITTLE_ENDIAN
@@ -2060,8 +2058,7 @@ static struct bpf_test tests[] = {#endif BPF_EXIT_INSN(), }, - .errstr = "invalid bpf_context access", - .result =REJECT , + .result =ACCEPT , }, { "check cb access: byte, wrong type",
@@ -2173,7 +2170,7 @@ static struct bpf_test tests[] = { .result = ACCEPT, }, { - "check skb->hash half load not permitted", + "check skb->hash half load permitted 2", .insns = {BPF_MOV64_IMM (BPF_REG_0 ,0 ), #if __BYTE_ORDER == __LITTLE_ENDIAN
@@ -2182,6 +2179,37 @@ static struct bpf_test tests[] = {#else BPF_LDX_MEM (BPF_H ,BPF_REG_0 ,BPF_REG_1 , offsetof(struct __sk_buff , hash)), +#endif + BPF_EXIT_INSN(), + }, + .result =ACCEPT , + }, + { +"check skb->hash half load not permitted, unaligned 1" , + .insns = { +BPF_MOV64_IMM (BPF_REG_0 ,0 ), +#if __BYTE_ORDER == __LITTLE_ENDIAN +BPF_LDX_MEM (BPF_H ,BPF_REG_0 ,BPF_REG_1 , + offsetof(struct __sk_buff , hash) +1 ), +#else +BPF_LDX_MEM (BPF_H ,BPF_REG_0 ,BPF_REG_1 , + offsetof(struct __sk_buff , hash) +3 ), +#endif + BPF_EXIT_INSN(), + }, + .errstr ="invalid bpf_context access" , + .result = REJECT, + }, + { +"check skb->hash half load not permitted, unaligned 3" , + .insns = { +BPF_MOV64_IMM (BPF_REG_0 ,0 ), +#if __BYTE_ORDER == __LITTLE_ENDIAN +BPF_LDX_MEM (BPF_H ,BPF_REG_0 ,BPF_REG_1 , + offsetof(struct __sk_buff , hash) +3 ), +#else +BPF_LDX_MEM (BPF_H ,BPF_REG_0 ,BPF_REG_1 , + offsetof(struct __sk_buff , hash) +1 ), #endif BPF_EXIT_INSN(), },
Signature · 1 line
2.17.1
PATCH v2 3/3 selftests/bpf: Test narrow loads with off > 0 for bpf_sock_addr
In reply to Andrey IgnatovAdd more test cases for context bpf_sock_addr to test narrow loads with offset > 0 for ctx->user_ip4 field (__u32):
* off=1, size=1;
* off=2, size=1;
* off=3, size=1;
* off=2, size=2.
--- tools/testing/selftests/bpf/test_sock_addr.c | 28 +++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-)
tools/testing/selftests/bpf/test_sock_addr.c +24 -4
@@ -574,24 +574,44 @@ static int bind4_prog_load(const struct sock_addr_test *test)/* if (sk.family == AF_INET && */ BPF_LDX_MEM (BPF_W ,BPF_REG_7 ,BPF_REG_6 , offsetof(struct bpf_sock_addr , family)), -BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,AF_INET ,16 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,AF_INET ,24 ),/* (sk.type == SOCK_DGRAM || sk.type == SOCK_STREAM) && */ BPF_LDX_MEM (BPF_W ,BPF_REG_7 ,BPF_REG_6 , offsetof(struct bpf_sock_addr , type)),BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,SOCK_DGRAM ,1 ),BPF_JMP_A (1 ), -BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,SOCK_STREAM ,12 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,SOCK_STREAM ,20 ),/* 1st_byte_of_user_ip4 == expected && */ BPF_LDX_MEM (BPF_B ,BPF_REG_7 ,BPF_REG_6 , offsetof(struct bpf_sock_addr , user_ip4)), -BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr8[0 ],10 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr8[0 ],18 ), + +/* 2nd_byte_of_user_ip4 == expected && */ +BPF_LDX_MEM (BPF_B ,BPF_REG_7 ,BPF_REG_6 , + offsetof(struct bpf_sock_addr , user_ip4) +1 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr8[1 ],16 ), + +/* 3rd_byte_of_user_ip4 == expected && */ +BPF_LDX_MEM (BPF_B ,BPF_REG_7 ,BPF_REG_6 , + offsetof(struct bpf_sock_addr , user_ip4) +2 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr8[2 ],14 ), + +/* 4th_byte_of_user_ip4 == expected && */ +BPF_LDX_MEM (BPF_B ,BPF_REG_7 ,BPF_REG_6 , + offsetof(struct bpf_sock_addr , user_ip4) +3 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr8[3 ],12 ),/* 1st_half_of_user_ip4 == expected && */ BPF_LDX_MEM (BPF_H ,BPF_REG_7 ,BPF_REG_6 , offsetof(struct bpf_sock_addr , user_ip4)), -BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr16[0 ],8 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr16[0 ],10 ), + +/* 2nd_half_of_user_ip4 == expected && */ +BPF_LDX_MEM (BPF_H ,BPF_REG_7 ,BPF_REG_6 , + offsetof(struct bpf_sock_addr , user_ip4) +2 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 , ip4.u4_addr16[1 ],8 ),/* whole_user_ip4 == expected) { */ BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
Signature · 1 line
2.17.1
Alexei Starovoitov
AppliedQuoted 9 lines · Andrey Ignatov
This patch set adds support for narrow loads with offset > 0 to BPF verifier.
Patch 1 provides more details and is the main patch in the set. Patches 2 and 3 add new test cases to
test_verifierandtest_sock_addrselftests.
v1->v2:
- fix -Wdeclaration-after-statement warning.
Applied, Thanks