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).

Reported-by: Yonghong Song <y...@fb.com>
Signed-off-by: Andrey Ignatov <r...@fb.com>
---
 include/linux/filter.h | 16 +---------------
 kernel/bpf/verifier.c  | 19 +++++++++++++++----
 2 files changed, 16 insertions(+), 19 deletions(-)

diff --git a/include/linux/filter.h b/include/linux/filter.h
index de629b706d1d..cc17f5f32fbb 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -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]))
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1971ca325fb4..fa592502568e 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5803,9 +5803,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;
+               u32 size_default = bpf_ctx_off_adjust_machine(ctx_field_size);
+               u32 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) {
@@ -5833,12 +5833,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);
-- 
2.17.1

Reply via email to