Post content
最近又在和 bpf verifier 肉搏了,但这次处理地很有条理,得意分享一下 🤔 u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; bpf_skb_load_bytes(skb, ..., ..., copy_len); 上面的 bpf c 会被 6.1 内核拒绝,原因是 bpf_skb_load_bytes 第四参数不能为零值: 350: (85) call bpf_skb_load_bytes#26 invalid zero-sized read 显然应该加上 copy_len 非零的检查 u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; if (!copy_len) return; bpf_skb_load_bytes(skb, ..., ..., copy_len); 但是依然相同的零值错误 ; if (!copy_len) 342: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ; if (!copy_len) 343: (15) if r4 == 0x0 goto pc-153 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ... 351: (85) call bpf_skb_load_bytes#26 invalid zero-sized read 仔细看一下, if (!copy_len) 的分支是生效的, if r4 == 0x0 goto pc-153,但是 verifier 没有更新 r4 的下界 umin。 也许应该把非零检查改为下界检查? u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; if (copy_len > 0) goto cont2; return; cont2: bpf_skb_load_bytes(skb, ..., ..., copy_len); 但这次还是一样的 invalid zero-sized read 😀 ; if (copy_len > 0) 342: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ; if (copy_len > 0) 343: (15) if r4 == 0x0 goto pc-153 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ... 351: (85) call bpf_skb_load_bytes#26 invalid zero-sized read processed 12902 insns (limit 1000000) max_states_per_insn 11 total_states 287 peak_states 167 mark_read 17 对着 verifier log 冥想足够长的时间,发现 if (copy_len>0) 被 clang 编译为了 r4 &= 255 和 if r4 == 0x0 goto,难怪 verifier 无法生成 r4 的 umin。 clang 太坏了,它怎么就不能生成 if r4 > 0 goto 呢? 生气了,手冲 asm,这下 clang 不会给我乱编译字节码了吧! u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; asm volatile goto( "if %[len] s> 0 goto %l[cont2]\n\t" : : [len] "r"(copy_len) : "memory" : cont2 ); return; cont2: bpf_skb_load_bytes(skb, ..., ..., copy_len); 然后还是得到 zero read ; asm volatile goto( 342: (65) if r4 s> 0x0 goto pc+1 344: R0=48 R1=0 R2=0 R3=32 R4=scalar(id=1099,umin=1,umax=999,var_off=(0x0; 0x3ff)) R5=fp-17 R6=scalar(id=1093,smin=-120,smax=4294967295) R7=ctx(off=0,imm=0) R8=0 R9=scalar(id=1094,umin=7,umax=127,var_off=(0x3; 0x7c)) R10=fp0 fp-16=mmmmmm?? fp-24=00000000 fp-32=00000000 fp-40=00000000 fp-48=00000000 fp-56=mmmm0000 fp-64=00000000 fp-72=00000000 fp-80=00000000 fp-88=00000000 fp-96=00000000 fp-104=00000000 fp-112=00000000 fp-120=00000000 fp-128=00000000 fp-136=00000000 fp-144=00000000 fp-152=00000000 fp-160=00000000 fp-168=00000000 fp-176=00000000 fp-184=00000000 fp-192=00000000 fp-200=00000000 fp-208=00000000 fp-216=00000000 fp-224=00000000 fp-232=00000000 fp-240=00000000 fp-248=00000000 fp-256=00000000 fp-264=00000000 fp-272=00000000 fp-280=00000000 fp-288=00000000 fp-296=00000000 fp-304=00000000 fp-312=00000000 fp-320=00000000 fp-328=mmmmmmmm fp-336=map_value fp-344=map_value ; offset += 1 + key_size + 2; 344: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ... 352: (85) call bpf_skb_load_bytes#26 invalid zero-sized read 等等,上面的日志好像已经对了,在我们手动注入 if r4 s> 0x0 goto pc+1 之后,verifier 给 r4 打上了正确的下界 umin=1 R4=scalar(id=1099,umin=1,umax=999,var_off=(0x0; 0x3ff)) 但是随后的 r4 &= 255 又把 umin=1 抹平了 344: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) 为什么 clang 生成了一句 r4 &= 255?对着 Piplup 小企鹅发呆了一会儿,我意识到是 copy_len 的类型导致的,我的类型定义是 u8 copy_len 因为它够小够用,然而 bpf_skb_load_bytes 类型声明里第四参数是 u32,所以相当于有个隐式的类型转换 bpf_skb_load_bytes(skb, ..., ..., (u32)copy_len); 这个类型转化导致 clang 生成了一句 r4 &= 255 字节码,导致 verifier 六亲不认销毁 umin。 最后把 copy_len 改为 u32,大成功! u32 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; asm volatile goto( "if %[len] s> 0 goto %l[cont2]\n\t" : : [len] "r"(copy_len) : "memory" : cont2 ); return; cont2: bpf_skb_load_bytes(skb, ..., ..., copy_len); 我感到自己已经逐渐理解 verifier 了,升格为弱等神力了 🤔