netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [bpf PATCH v2] bpf: verifier, do_refine_retval_range may clamp umin to 0 incorrectly
@ 2020-01-26 22:20 John Fastabend
  2020-01-27 14:51 ` Daniel Borkmann
  0 siblings, 1 reply; 3+ messages in thread
From: John Fastabend @ 2020-01-26 22:20 UTC (permalink / raw)
  To: bpf; +Cc: yhs, john.fastabend, ast, daniel, netdev

do_refine_retval_range() is called to refine return values from specified
helpers, probe_read_str and get_stack at the moment, the reasoning is
because both have a max value as part of their input arguments and
because the helper ensure the return value will not be larger than this
we can set smax values of the return register, r0.

However, the return value is a signed integer so setting umax is incorrect
It leads to further confusion when the do_refine_retval_range() then calls,
__reg_deduce_bounds() which will see a umax value as meaning the value is
unsigned and then assuming it is unsigned set the smin = umin which in this
case results in 'smin = 0' and an 'smax = X' where X is the input argument
from the helper call.

Here are the comments from _reg_deduce_bounds() on why this would be safe
to do.

 /* Learn sign from unsigned bounds.  Signed bounds cross the sign
  * boundary, so we must be careful.
  */
 if ((s64)reg->umax_value >= 0) {
	/* Positive.  We can't learn anything from the smin, but smax
	 * is positive, hence safe.
	 */
	reg->smin_value = reg->umin_value;
	reg->smax_value = reg->umax_value = min_t(u64, reg->smax_value,
						  reg->umax_value);

But now we incorrectly have a return value with type int with the
signed bounds (0,X). Suppose the return value is negative, which is
possible the we have the verifier and reality out of sync. Among other
things this may result in any error handling code being falsely detected
as dead-code and removed. For instance the example below shows using
bpf_probe_read_str() causes the error path to be identified as dead
code and removed.

>From the 'llvm-object -S' dump,

 r2 = 100
 call 45
 if r0 s< 0 goto +4
 r4 = *(u32 *)(r7 + 0)

But from dump xlate

  (b7) r2 = 100
  (85) call bpf_probe_read_compat_str#-96768
  (61) r4 = *(u32 *)(r7 +0)  <-- dropped if goto

Due to verifier state after call being

 R0=inv(id=0,umax_value=100,var_off=(0x0; 0x7f))

To fix omit setting the umax value because its not safe. The only
actual bounds we know is the smax. This results in the correct bounds
(SMIN, X) where X is the max length from the helper. After this the
new verifier state looks like the following after call 45.

R0=inv(id=0,smax_value=100)

Then xlated version no longer removed dead code giving the expected
result,

  (b7) r2 = 100
  (85) call bpf_probe_read_compat_str#-96768
  (c5) if r0 s< 0x0 goto pc+4
  (61) r4 = *(u32 *)(r7 +0)

Note, bpf_probe_read_* calls are root only so we wont hit this case
with non-root bpf users.

v2 note: In original version we set msize_smax_value from check_func_arg()
and propagated this into smax of retval. The logic was smax is the bound
on the retval we set and because the type in the helper is ARG_CONST_SIZE
we know that the reg is a positive tnum_const() so umax=smax. Alexei
pointed out though this is a bit odd to read because the register in
check_func_arg() has a C type of u32 and the umax bound would be the
normally relavent bound here. Pulling in extra knowledge about future
checks makes reading the code a bit tricky. Further having a signed
meta data that can only ever be positive is also a bit odd. So dropped
the msize_smax_value metadata and made it a u64 msize_max_Value to
indicate its unsigned. And additionally save bound from umax value in
check_arg_funcs which is the same as smax due to as noted above tnumx_cont
and negative check but reads better. By my analysis nothing functionally
changes in v2 but it does get easier to read so that is win.

Fixes: 849fa50662fbc ("bpf: verifier, refine bounds may clamp umin to 0 incorrectly")
Signed-off-by: John Fastabend <john.fastabend@gmail.com>
---
 kernel/bpf/verifier.c |   20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7d530ce8719d..1c63436510d8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -227,8 +227,7 @@ struct bpf_call_arg_meta {
 	bool pkt_access;
 	int regno;
 	int access_size;
-	s64 msize_smax_value;
-	u64 msize_umax_value;
+	u64 msize_max_value;
 	int ref_obj_id;
 	int func_id;
 	u32 btf_id;
@@ -3569,11 +3568,16 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
 	} else if (arg_type_is_mem_size(arg_type)) {
 		bool zero_size_allowed = (arg_type == ARG_CONST_SIZE_OR_ZERO);
 
-		/* remember the mem_size which may be used later
-		 * to refine return values.
+		/* This is used to refine r0 return value bounds for helpers
+		 * that enforce this value as an upper bound on return values.
+		 * See do_refine_retval_range() for helpers that can refine
+		 * the return value. C type of helper is u32 so we pull register
+		 * bound from umax_value however, if not a const then meta
+		 * is null'd and if negative verifier errors out. Only upper
+		 * bounds can be learned because retval is an int type and
+		 * negative retvals are allowed.
 		 */
-		meta->msize_smax_value = reg->smax_value;
-		meta->msize_umax_value = reg->umax_value;
+		meta->msize_max_value = reg->umax_value;
 
 		/* The register is SCALAR_VALUE; the access check
 		 * happens using its boundaries.
@@ -4077,10 +4081,10 @@ static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type,
 	     func_id != BPF_FUNC_probe_read_str))
 		return;
 
-	ret_reg->smax_value = meta->msize_smax_value;
-	ret_reg->umax_value = meta->msize_umax_value;
+	ret_reg->smax_value = meta->msize_max_value;
 	__reg_deduce_bounds(ret_reg);
 	__reg_bound_offset(ret_reg);
+	__update_reg_bounds(ret_reg);
 }
 
 static int


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [bpf PATCH v2] bpf: verifier, do_refine_retval_range may clamp umin to 0 incorrectly
  2020-01-26 22:20 [bpf PATCH v2] bpf: verifier, do_refine_retval_range may clamp umin to 0 incorrectly John Fastabend
@ 2020-01-27 14:51 ` Daniel Borkmann
  2020-01-27 16:32   ` John Fastabend
  0 siblings, 1 reply; 3+ messages in thread
From: Daniel Borkmann @ 2020-01-27 14:51 UTC (permalink / raw)
  To: John Fastabend, bpf; +Cc: yhs, ast, netdev

On 1/26/20 11:20 PM, John Fastabend wrote:
> do_refine_retval_range() is called to refine return values from specified
> helpers, probe_read_str and get_stack at the moment, the reasoning is
> because both have a max value as part of their input arguments and
> because the helper ensure the return value will not be larger than this
> we can set smax values of the return register, r0.
> 
> However, the return value is a signed integer so setting umax is incorrect
> It leads to further confusion when the do_refine_retval_range() then calls,
> __reg_deduce_bounds() which will see a umax value as meaning the value is
> unsigned and then assuming it is unsigned set the smin = umin which in this
> case results in 'smin = 0' and an 'smax = X' where X is the input argument
> from the helper call.
> 
> Here are the comments from _reg_deduce_bounds() on why this would be safe
> to do.
> 
>   /* Learn sign from unsigned bounds.  Signed bounds cross the sign
>    * boundary, so we must be careful.
>    */
>   if ((s64)reg->umax_value >= 0) {
> 	/* Positive.  We can't learn anything from the smin, but smax
> 	 * is positive, hence safe.
> 	 */
> 	reg->smin_value = reg->umin_value;
> 	reg->smax_value = reg->umax_value = min_t(u64, reg->smax_value,
> 						  reg->umax_value);
> 
> But now we incorrectly have a return value with type int with the
> signed bounds (0,X). Suppose the return value is negative, which is
> possible the we have the verifier and reality out of sync. Among other
> things this may result in any error handling code being falsely detected
> as dead-code and removed. For instance the example below shows using
> bpf_probe_read_str() causes the error path to be identified as dead
> code and removed.
> 
>>From the 'llvm-object -S' dump,
> 
>   r2 = 100
>   call 45
>   if r0 s< 0 goto +4
>   r4 = *(u32 *)(r7 + 0)
> 
> But from dump xlate
> 
>    (b7) r2 = 100
>    (85) call bpf_probe_read_compat_str#-96768
>    (61) r4 = *(u32 *)(r7 +0)  <-- dropped if goto
> 
> Due to verifier state after call being
> 
>   R0=inv(id=0,umax_value=100,var_off=(0x0; 0x7f))
> 
> To fix omit setting the umax value because its not safe. The only
> actual bounds we know is the smax. This results in the correct bounds
> (SMIN, X) where X is the max length from the helper. After this the
> new verifier state looks like the following after call 45.
> 
> R0=inv(id=0,smax_value=100)
> 
> Then xlated version no longer removed dead code giving the expected
> result,
> 
>    (b7) r2 = 100
>    (85) call bpf_probe_read_compat_str#-96768
>    (c5) if r0 s< 0x0 goto pc+4
>    (61) r4 = *(u32 *)(r7 +0)
> 
> Note, bpf_probe_read_* calls are root only so we wont hit this case
> with non-root bpf users.
> 
> v2 note: In original version we set msize_smax_value from check_func_arg()
> and propagated this into smax of retval. The logic was smax is the bound
> on the retval we set and because the type in the helper is ARG_CONST_SIZE
> we know that the reg is a positive tnum_const() so umax=smax. Alexei
> pointed out though this is a bit odd to read because the register in
> check_func_arg() has a C type of u32 and the umax bound would be the
> normally relavent bound here. Pulling in extra knowledge about future
> checks makes reading the code a bit tricky. Further having a signed
> meta data that can only ever be positive is also a bit odd. So dropped
> the msize_smax_value metadata and made it a u64 msize_max_Value to
> indicate its unsigned. And additionally save bound from umax value in
> check_arg_funcs which is the same as smax due to as noted above tnumx_cont
> and negative check but reads better. By my analysis nothing functionally
> changes in v2 but it does get easier to read so that is win.
> 
> Fixes: 849fa50662fbc ("bpf: verifier, refine bounds may clamp umin to 0 incorrectly")
> Signed-off-by: John Fastabend <john.fastabend@gmail.com>

The Fixes tag is not correct. I presume you meant to say:

Fixes: 849fa50662fbc ("bpf/verifier: refine retval R0 state for bpf_get_stack helper")

> ---
>   kernel/bpf/verifier.c |   20 ++++++++++++--------
>   1 file changed, 12 insertions(+), 8 deletions(-)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7d530ce8719d..1c63436510d8 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -227,8 +227,7 @@ struct bpf_call_arg_meta {
>   	bool pkt_access;
>   	int regno;
>   	int access_size;
> -	s64 msize_smax_value;
> -	u64 msize_umax_value;
> +	u64 msize_max_value;
>   	int ref_obj_id;
>   	int func_id;
>   	u32 btf_id;
> @@ -3569,11 +3568,16 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
>   	} else if (arg_type_is_mem_size(arg_type)) {
>   		bool zero_size_allowed = (arg_type == ARG_CONST_SIZE_OR_ZERO);
>   
> -		/* remember the mem_size which may be used later
> -		 * to refine return values.
> +		/* This is used to refine r0 return value bounds for helpers
> +		 * that enforce this value as an upper bound on return values.
> +		 * See do_refine_retval_range() for helpers that can refine
> +		 * the return value. C type of helper is u32 so we pull register
> +		 * bound from umax_value however, if not a const then meta
> +		 * is null'd and if negative verifier errors out. Only upper

The 'meta is null'd' part for non-const, isn't this irrelevant in this case? Meaning,
we'll still adapt the ret register with msize_max_value in this case and it provides
the upper bound same way as with const reg (just for latter that min==max).

> +		 * bounds can be learned because retval is an int type and
> +		 * negative retvals are allowed.
>   		 */
> -		meta->msize_smax_value = reg->smax_value;
> -		meta->msize_umax_value = reg->umax_value;
> +		meta->msize_max_value = reg->umax_value;
>   
>   		/* The register is SCALAR_VALUE; the access check
>   		 * happens using its boundaries.
> @@ -4077,10 +4081,10 @@ static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type,
>   	     func_id != BPF_FUNC_probe_read_str))
>   		return;
>   
> -	ret_reg->smax_value = meta->msize_smax_value;
> -	ret_reg->umax_value = meta->msize_umax_value;
> +	ret_reg->smax_value = meta->msize_max_value;
>   	__reg_deduce_bounds(ret_reg);
>   	__reg_bound_offset(ret_reg);
> +	__update_reg_bounds(ret_reg);
>   }
>   
>   static int
> 


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [bpf PATCH v2] bpf: verifier, do_refine_retval_range may clamp umin to 0 incorrectly
  2020-01-27 14:51 ` Daniel Borkmann
@ 2020-01-27 16:32   ` John Fastabend
  0 siblings, 0 replies; 3+ messages in thread
From: John Fastabend @ 2020-01-27 16:32 UTC (permalink / raw)
  To: Daniel Borkmann, John Fastabend, bpf; +Cc: yhs, ast, netdev

Daniel Borkmann wrote:
> On 1/26/20 11:20 PM, John Fastabend wrote:
> > do_refine_retval_range() is called to refine return values from specified
> > helpers, probe_read_str and get_stack at the moment, the reasoning is
> > because both have a max value as part of their input arguments and
> > because the helper ensure the return value will not be larger than this
> > we can set smax values of the return register, r0.
> > 
> > However, the return value is a signed integer so setting umax is incorrect
> > It leads to further confusion when the do_refine_retval_range() then calls,
> > __reg_deduce_bounds() which will see a umax value as meaning the value is
> > unsigned and then assuming it is unsigned set the smin = umin which in this
> > case results in 'smin = 0' and an 'smax = X' where X is the input argument
> > from the helper call.
> > 
> > Here are the comments from _reg_deduce_bounds() on why this would be safe
> > to do.
> > 
> >   /* Learn sign from unsigned bounds.  Signed bounds cross the sign
> >    * boundary, so we must be careful.
> >    */
> >   if ((s64)reg->umax_value >= 0) {
> > 	/* Positive.  We can't learn anything from the smin, but smax
> > 	 * is positive, hence safe.
> > 	 */
> > 	reg->smin_value = reg->umin_value;
> > 	reg->smax_value = reg->umax_value = min_t(u64, reg->smax_value,
> > 						  reg->umax_value);
> > 
> > But now we incorrectly have a return value with type int with the
> > signed bounds (0,X). Suppose the return value is negative, which is
> > possible the we have the verifier and reality out of sync. Among other
> > things this may result in any error handling code being falsely detected
> > as dead-code and removed. For instance the example below shows using
> > bpf_probe_read_str() causes the error path to be identified as dead
> > code and removed.
> > 
> >>From the 'llvm-object -S' dump,
> > 
> >   r2 = 100
> >   call 45
> >   if r0 s< 0 goto +4
> >   r4 = *(u32 *)(r7 + 0)
> > 
> > But from dump xlate
> > 
> >    (b7) r2 = 100
> >    (85) call bpf_probe_read_compat_str#-96768
> >    (61) r4 = *(u32 *)(r7 +0)  <-- dropped if goto
> > 
> > Due to verifier state after call being
> > 
> >   R0=inv(id=0,umax_value=100,var_off=(0x0; 0x7f))
> > 
> > To fix omit setting the umax value because its not safe. The only
> > actual bounds we know is the smax. This results in the correct bounds
> > (SMIN, X) where X is the max length from the helper. After this the
> > new verifier state looks like the following after call 45.
> > 
> > R0=inv(id=0,smax_value=100)
> > 
> > Then xlated version no longer removed dead code giving the expected
> > result,
> > 
> >    (b7) r2 = 100
> >    (85) call bpf_probe_read_compat_str#-96768
> >    (c5) if r0 s< 0x0 goto pc+4
> >    (61) r4 = *(u32 *)(r7 +0)
> > 
> > Note, bpf_probe_read_* calls are root only so we wont hit this case
> > with non-root bpf users.
> > 
> > v2 note: In original version we set msize_smax_value from check_func_arg()
> > and propagated this into smax of retval. The logic was smax is the bound
> > on the retval we set and because the type in the helper is ARG_CONST_SIZE
> > we know that the reg is a positive tnum_const() so umax=smax. Alexei
> > pointed out though this is a bit odd to read because the register in
> > check_func_arg() has a C type of u32 and the umax bound would be the
> > normally relavent bound here. Pulling in extra knowledge about future
> > checks makes reading the code a bit tricky. Further having a signed
> > meta data that can only ever be positive is also a bit odd. So dropped
> > the msize_smax_value metadata and made it a u64 msize_max_Value to
> > indicate its unsigned. And additionally save bound from umax value in
> > check_arg_funcs which is the same as smax due to as noted above tnumx_cont
> > and negative check but reads better. By my analysis nothing functionally
> > changes in v2 but it does get easier to read so that is win.
> > 
> > Fixes: 849fa50662fbc ("bpf: verifier, refine bounds may clamp umin to 0 incorrectly")
> > Signed-off-by: John Fastabend <john.fastabend@gmail.com>
> 
> The Fixes tag is not correct. I presume you meant to say:
> 
> Fixes: 849fa50662fbc ("bpf/verifier: refine retval R0 state for bpf_get_stack helper")

correct thanks.

> 
> > ---
> >   kernel/bpf/verifier.c |   20 ++++++++++++--------
> >   1 file changed, 12 insertions(+), 8 deletions(-)
> > 
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 7d530ce8719d..1c63436510d8 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -227,8 +227,7 @@ struct bpf_call_arg_meta {
> >   	bool pkt_access;
> >   	int regno;
> >   	int access_size;
> > -	s64 msize_smax_value;
> > -	u64 msize_umax_value;
> > +	u64 msize_max_value;
> >   	int ref_obj_id;
> >   	int func_id;
> >   	u32 btf_id;
> > @@ -3569,11 +3568,16 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
> >   	} else if (arg_type_is_mem_size(arg_type)) {
> >   		bool zero_size_allowed = (arg_type == ARG_CONST_SIZE_OR_ZERO);
> >   
> > -		/* remember the mem_size which may be used later
> > -		 * to refine return values.
> > +		/* This is used to refine r0 return value bounds for helpers
> > +		 * that enforce this value as an upper bound on return values.
> > +		 * See do_refine_retval_range() for helpers that can refine
> > +		 * the return value. C type of helper is u32 so we pull register
> > +		 * bound from umax_value however, if not a const then meta
> > +		 * is null'd and if negative verifier errors out. Only upper
> 
> The 'meta is null'd' part for non-const, isn't this irrelevant in this case? Meaning,
> we'll still adapt the ret register with msize_max_value in this case and it provides
> the upper bound same way as with const reg (just for latter that min==max).

Agreed unrelevant info I'll remove it so we don't confuse people.

> 
> > +		 * bounds can be learned because retval is an int type and
> > +		 * negative retvals are allowed.
> >   		 */
> > -		meta->msize_smax_value = reg->smax_value;
> > -		meta->msize_umax_value = reg->umax_value;
> > +		meta->msize_max_value = reg->umax_value;
> >   
> >   		/* The register is SCALAR_VALUE; the access check
> >   		 * happens using its boundaries.
> > @@ -4077,10 +4081,10 @@ static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type,
> >   	     func_id != BPF_FUNC_probe_read_str))
> >   		return;
> >   
> > -	ret_reg->smax_value = meta->msize_smax_value;
> > -	ret_reg->umax_value = meta->msize_umax_value;
> > +	ret_reg->smax_value = meta->msize_max_value;
> >   	__reg_deduce_bounds(ret_reg);
> >   	__reg_bound_offset(ret_reg);
> > +	__update_reg_bounds(ret_reg);
> >   }
> >   
> >   static int
> > 
> 



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2020-01-27 16:32 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-26 22:20 [bpf PATCH v2] bpf: verifier, do_refine_retval_range may clamp umin to 0 incorrectly John Fastabend
2020-01-27 14:51 ` Daniel Borkmann
2020-01-27 16:32   ` John Fastabend

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).