bpf: Fix possible out of bound write in narrow load handling
PATCH 1/1 bpf: Fix possible out of bound write in narrow load handling
Fix a verifier bug found by smatch static checker in [0].
When narrow load is handled, one or two new instructions are added to insn_buf array, but before it was only checked that
cnt >= ARRAY_SIZE(insn_buf)
And it's safe to add a new instruction to insn_buf[cnt++] only once. The second try will lead to out of bound write. And this is what can happen if shift is set.
Fix it by making sure that if the BPF_RSH instruction has to be added in addition to BPF_AND then there is enough space for two more instructions in insn_buf.
The full report [0] is below:
kernel/bpf/verifier.c:12304 convert_ctx_accesses() warn: offset 'cnt' incremented past end of array kernel/bpf/verifier.c:12311 convert_ctx_accesses() warn: offset 'cnt' incremented past end of array
kernel/bpf/verifier.c
12282
12283 insn->off = off & ~(size_default - 1);
12284 insn->code = BPF_LDX | BPF_MEM | size_code;
12285 }
12286
12287 target_size = 0;
12288 cnt = convert_ctx_access(type, insn, insn_buf, env->prog,
12289 &target_size);
12290 if (cnt == 0 || cnt >= ARRAY_SIZE(insn_buf) ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^Bounds check.
12291 (ctx_field_size && !target_size)) {
12292 verbose(env, "bpf verifier is misconfigured\n");
12293 return -EINVAL;
12294 }
12295
12296 if (is_narrower_load && size < target_size) {
12297 u8 shift = bpf_ctx_narrow_access_offset(
12298 off, size, size_default) * 8;
12299 if (ctx_field_size <= 4) {
12300 if (shift)
12301 insn_buf[cnt++] = BPF_ALU32_IMM(BPF_RSH,
^^^^^increment beyond end of array
12302 insn->dst_reg,
12303 shift);
--> 12304 insn_buf[cnt++] = BPF_ALU32_IMM(BPF_AND, insn->dst_reg,
^^^^^out of bounds write
12305 (1 << size * 8) - 1);
12306 } else {
12307 if (shift)
12308 insn_buf[cnt++] = BPF_ALU64_IMM(BPF_RSH,
12309 insn->dst_reg,
12310 shift);
12311 insn_buf[cnt++] = BPF_ALU64_IMM(BPF_AND, insn->dst_reg,
^^^^^^^^^^^^^^^Same.
12312 (1ULL << size * 8) - 1);
12313 }
12314 }
12315
12316 new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
12317 if (!new_prog)
12318 return -ENOMEM;
12319
12320 delta += cnt - 1;
12321
12322 /* keep walking new program and skip insns we just inserted */
12323 env->prog = new_prog;
12324 insn = new_prog->insnsi + i + delta;
12325 }
12326
12327 return 0;
12328 }[0] https://lore.kernel.org/bpf/20210817050843.GA21456@kili/
--- kernel/bpf/verifier.c | 4 ++++ 1 file changed, 4 insertions(+)
kernel/bpf/verifier.c +4 -0
@@ -12004,6 +12004,10 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)if (is_narrower_load && size < target_size) {u8 shift =bpf_ctx_narrow_access_offset ( off, size, size_default) *8 ; +if (shift && cnt +1 >=ARRAY_SIZE (insn_buf)) { +verbose (env,"bpf verifier narrow ctx load misconfigured\n" ); +return -EINVAL ; + }if (ctx_field_size <=4 ) {if (shift) insn_buf[cnt++] = BPF_ALU32_IMM(BPF_RSH,
Signature · 1 line
2.30.2
Daniel Borkmann
maintainerQuoted 10 lines · Andrey Ignatov
Fix a verifier bug found by smatch static checker in [0].
When narrow load is handled, one or two new instructions are added to
insn_bufarray, but before it was only checked thatcnt >=
ARRAY_SIZE(insn_buf)And it's safe to add a new instruction to insn_buf[cnt++] only once. The second try will lead to out of bound write. And this is what can happen if
shiftis set.
Afaik, the insn_buf[] should always be large enough, could you add something to the commit message of this fix whether this created an actual issue in practice where we do oob overrun insn_buf[] (or whether it's 'only' the static checker report ... from above paragraph I read you saw the former in practice)?
Thanks,
Daniel
Daniel Borkmann <[email protected]> [Wed, 2021-08-18 15:48 -0700]:
Quoted 8 lines · Andrey Ignatov
Quoted 7 lines
Fix a verifier bug found by smatch static checker in [0].
When narrow load is handled, one or two new instructions are added to
insn_bufarray, but before it was only checked thatcnt >=
ARRAY_SIZE(insn_buf)
Quoted 8 lines
Quoted 3 lines
And it's safe to add a new instruction to insn_buf[cnt++] only once. The second try will lead to out of bound write. And this is what can happen if
shiftis set.Afaik, the insn_buf[] should always be large enough, could you add something to the commit message of this fix whether this created an actual issue in practice where we do oob overrun insn_buf[] (or whether it's 'only' the static checker report ... from above paragraph I read you saw the former in practice)?
It's 'only' the static checker report. I've never seen this problem in practice.
I also have an impression that convert_ctx_accesses should not in general (or should never?) return too many insn to hit insn_buf[] limit, but it may not be trivial to check all scenarios so I can't say for sure. That's why it makes sense to me to address the report.
Sure, I can add this info to the commit message in v2.
Signature · 1 line
Andrey Ignatov