diff mbox series

[v2,bpf-next,06/10] bpf: preserve constant zero when doing partial register restore

Message ID 20231121002221.3687787-7-andrii@kernel.org (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Complete BPF verifier precision tracking support for register spills | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / test
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-3 fail Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for s390x-gcc / test
bpf/vmtest-bpf-next-VM_Test-6 fail Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-8 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next-VM_Test-10 fail Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for x86_64-gcc / test
bpf/vmtest-bpf-next-VM_Test-12 success Logs for x86_64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-13 fail Logs for x86_64-llvm-16 / build / build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-14 success Logs for x86_64-llvm-16 / test
bpf/vmtest-bpf-next-VM_Test-15 success Logs for x86_64-llvm-16 / veristat
netdev/series_format success Posting correctly formatted
netdev/codegen success Generated files up to date
netdev/tree_selection success Clearly marked for bpf-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1146 this patch: 1146
netdev/cc_maintainers warning 8 maintainers not CCed: yonghong.song@linux.dev john.fastabend@gmail.com song@kernel.org haoluo@google.com jolsa@kernel.org kpsingh@kernel.org martin.lau@linux.dev sdf@google.com
netdev/build_clang success Errors and warnings before: 1166 this patch: 1166
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1173 this patch: 1173
netdev/checkpatch warning WARNING: line length of 86 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 93 exceeds 80 columns WARNING: line length of 97 exceeds 80 columns WARNING: line length of 99 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Andrii Nakryiko Nov. 21, 2023, 12:22 a.m. UTC
Similar to special handling of STACK_ZERO, when reading 1/2/4 bytes from
stack from slot that has register spilled into it and that register has
a constant value zero, preserve that zero and mark spilled register as
precise for that. This makes spilled const zero register and STACK_ZERO
cases equivalent in their behavior.

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
 kernel/bpf/verifier.c | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

Comments

Eduard Zingerman Nov. 21, 2023, 4:20 p.m. UTC | #1
On Mon, 2023-11-20 at 16:22 -0800, Andrii Nakryiko wrote:
> Similar to special handling of STACK_ZERO, when reading 1/2/4 bytes from
> stack from slot that has register spilled into it and that register has
> a constant value zero, preserve that zero and mark spilled register as
> precise for that. This makes spilled const zero register and STACK_ZERO
> cases equivalent in their behavior.
> 
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

[...]

> +				if (spill_cnt == size &&
> +				    tnum_is_const(reg->var_off) && reg->var_off.value == 0) {
> +					__mark_reg_const_zero(&state->regs[dst_regno]);
> +					/* this IS register fill, so keep insn_flags */
> +				} else if (zero_cnt == size) {
> +					/* similarly to mark_reg_stack_read(), preserve zeroes */
> +					__mark_reg_const_zero(&state->regs[dst_regno]);
> +					insn_flags = 0; /* not restoring original register state */

nit: In case if there would be v3, could you please
     leave a comment here, something like below:
     
       when check_stack_write_fixed_off() puts STACK_ZERO marks
       for writes, not aligned on register boundary, it marks source
       registers precise. Thus, additional precision propagation is
       necessary in this case and insn_flags could be cleared.

     or something along these lines?

> +				} else {
> +					mark_reg_unknown(env, state->regs, dst_regno);
> +					insn_flags = 0; /* not restoring original register state */
> +				}
>  			}
>  			state->regs[dst_regno].live |= REG_LIVE_WRITTEN;
>  		} else if (dst_regno >= 0) {
Andrii Nakryiko Nov. 21, 2023, 6:14 p.m. UTC | #2
On Tue, Nov 21, 2023 at 8:20 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Mon, 2023-11-20 at 16:22 -0800, Andrii Nakryiko wrote:
> > Similar to special handling of STACK_ZERO, when reading 1/2/4 bytes from
> > stack from slot that has register spilled into it and that register has
> > a constant value zero, preserve that zero and mark spilled register as
> > precise for that. This makes spilled const zero register and STACK_ZERO
> > cases equivalent in their behavior.
> >
> > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > ---
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
> [...]
>
> > +                             if (spill_cnt == size &&
> > +                                 tnum_is_const(reg->var_off) && reg->var_off.value == 0) {
> > +                                     __mark_reg_const_zero(&state->regs[dst_regno]);
> > +                                     /* this IS register fill, so keep insn_flags */
> > +                             } else if (zero_cnt == size) {
> > +                                     /* similarly to mark_reg_stack_read(), preserve zeroes */
> > +                                     __mark_reg_const_zero(&state->regs[dst_regno]);
> > +                                     insn_flags = 0; /* not restoring original register state */
>
> nit: In case if there would be v3, could you please
>      leave a comment here, something like below:
>
>        when check_stack_write_fixed_off() puts STACK_ZERO marks
>        for writes, not aligned on register boundary, it marks source
>        registers precise. Thus, additional precision propagation is
>        necessary in this case and insn_flags could be cleared.
>
>      or something along these lines?
>

Hm... this seems misleading, precision propagation of original
register on write is orthogonal to this. The real reason why we clear
insn_flags is because there is no *register* fill here. There might
not be any spilled register at all here, or a completely irrelevant
spilled register. This instruction is basically `r1 = 0;`, though
obfuscated as stack dereference. So from the backtracking side of
things, there is no stack access here.



> > +                             } else {
> > +                                     mark_reg_unknown(env, state->regs, dst_regno);
> > +                                     insn_flags = 0; /* not restoring original register state */
> > +                             }
> >                       }
> >                       state->regs[dst_regno].live |= REG_LIVE_WRITTEN;
> >               } else if (dst_regno >= 0) {
>
>
>
Eduard Zingerman Nov. 21, 2023, 8:22 p.m. UTC | #3
On Tue, 2023-11-21 at 10:14 -0800, Andrii Nakryiko wrote:
> On Tue, Nov 21, 2023 at 8:20 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
> > 
> > On Mon, 2023-11-20 at 16:22 -0800, Andrii Nakryiko wrote:
> > > Similar to special handling of STACK_ZERO, when reading 1/2/4 bytes from
> > > stack from slot that has register spilled into it and that register has
> > > a constant value zero, preserve that zero and mark spilled register as
> > > precise for that. This makes spilled const zero register and STACK_ZERO
> > > cases equivalent in their behavior.
> > > 
> > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > > ---
> > 
> > Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> > 
> > [...]
> > 
> > > +                             if (spill_cnt == size &&
> > > +                                 tnum_is_const(reg->var_off) && reg->var_off.value == 0) {
> > > +                                     __mark_reg_const_zero(&state->regs[dst_regno]);
> > > +                                     /* this IS register fill, so keep insn_flags */
> > > +                             } else if (zero_cnt == size) {
> > > +                                     /* similarly to mark_reg_stack_read(), preserve zeroes */
> > > +                                     __mark_reg_const_zero(&state->regs[dst_regno]);
> > > +                                     insn_flags = 0; /* not restoring original register state */
> > 
> > nit: In case if there would be v3, could you please
> >      leave a comment here, something like below:
> > 
> >        when check_stack_write_fixed_off() puts STACK_ZERO marks
> >        for writes, not aligned on register boundary, it marks source
> >        registers precise. Thus, additional precision propagation is
> >        necessary in this case and insn_flags could be cleared.
> > 
> >      or something along these lines?
> > 
> 
> Hm... this seems misleading, precision propagation of original
> register on write is orthogonal to this. The real reason why we clear
> insn_flags is because there is no *register* fill here. There might
> not be any spilled register at all here, or a completely irrelevant
> spilled register. This instruction is basically `r1 = 0;`, though
> obfuscated as stack dereference. So from the backtracking side of
> things, there is no stack access here.

Hm, I mostly agree. This comment would be relevant only before patch #8
in this series.
diff mbox series

Patch

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5145afb5da25..00e6b17b71d0 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4695,22 +4695,39 @@  static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
 				copy_register_state(&state->regs[dst_regno], reg);
 				state->regs[dst_regno].subreg_def = subreg_def;
 			} else {
+				int spill_cnt = 0, zero_cnt = 0;
+
 				for (i = 0; i < size; i++) {
 					type = stype[(slot - i) % BPF_REG_SIZE];
-					if (type == STACK_SPILL)
+					if (type == STACK_SPILL) {
+						spill_cnt++;
 						continue;
+					}
 					if (type == STACK_MISC)
 						continue;
-					if (type == STACK_ZERO)
+					if (type == STACK_ZERO) {
+						zero_cnt++;
 						continue;
+					}
 					if (type == STACK_INVALID && env->allow_uninit_stack)
 						continue;
 					verbose(env, "invalid read from stack off %d+%d size %d\n",
 						off, i, size);
 					return -EACCES;
 				}
-				mark_reg_unknown(env, state->regs, dst_regno);
-				insn_flags = 0; /* not restoring original register state */
+
+				if (spill_cnt == size &&
+				    tnum_is_const(reg->var_off) && reg->var_off.value == 0) {
+					__mark_reg_const_zero(&state->regs[dst_regno]);
+					/* this IS register fill, so keep insn_flags */
+				} else if (zero_cnt == size) {
+					/* similarly to mark_reg_stack_read(), preserve zeroes */
+					__mark_reg_const_zero(&state->regs[dst_regno]);
+					insn_flags = 0; /* not restoring original register state */
+				} else {
+					mark_reg_unknown(env, state->regs, dst_regno);
+					insn_flags = 0; /* not restoring original register state */
+				}
 			}
 			state->regs[dst_regno].live |= REG_LIVE_WRITTEN;
 		} else if (dst_regno >= 0) {