diff mbox

[ovs-dev] byte-order: avoid left shifts with unrepresentable results

Message ID 20170612201328.473-1-lrichard@redhat.com
State Superseded
Headers show

Commit Message

Lance Richardson June 12, 2017, 8:13 p.m. UTC
A left shift that would produce a result that is not representable
by the type of the expression's result has "undefined behavior"
according to the C language standard. Avoid this by casting values
that could set the upper bit to unsigned types.

Found via gcc's undefined behavior sanitizer.

Signed-off-by: Lance Richardson <lrichard@redhat.com>
---
 lib/byte-order.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Comments

Gregory Rose June 12, 2017, 10:44 p.m. UTC | #1
On 06/12/2017 01:13 PM, Lance Richardson wrote:
> A left shift that would produce a result that is not representable
> by the type of the expression's result has "undefined behavior"
> according to the C language standard. Avoid this by casting values
> that could set the upper bit to unsigned types.
>
> Found via gcc's undefined behavior sanitizer.
>
> Signed-off-by: Lance Richardson <lrichard@redhat.com>
> ---
>   lib/byte-order.h | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/lib/byte-order.h b/lib/byte-order.h
> index e864658..782439f 100644
> --- a/lib/byte-order.h
> +++ b/lib/byte-order.h
> @@ -105,9 +105,9 @@ uint32_byteswap(uint32_t crc) {
>       (OVS_FORCE ovs_be32)((uint32_t)(B1) << 16 | (B2))
>   #else
>   #define BYTES_TO_BE32(B1, B2, B3, B4) \
> -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 8 | (B3) << 16 | (B4) << 24)
> +    (OVS_FORCE ovs_be32)((B1) | (B2) << 8 | (B3) << 16 | (uint32_t)(B4) << 24)

if B2 is a unsigned char then what is the value of this expression?
B2 << 8

Same here.  If B3 is an unsigned char what is the value of this expression?
B3 << 16

>   #define BE16S_TO_BE32(B1, B2) \
> -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 16)
> +    (OVS_FORCE ovs_be32)((B1) | (uint32_t)(B2) << 16)
>   #endif
>
>   /* These functions zero-extend big-endian values to longer ones,
>
I don't these macros.  There is no type checking so I think they could be improved.

I'd suggest turning them into inline functions so you get type checking, etc.

My $0.02$

Thanks,

- Greg
Lance Richardson June 12, 2017, 11:36 p.m. UTC | #2
----- Original Message -----
> From: "Greg Rose" <gvrose8192@gmail.com>
> To: "Lance Richardson" <lrichard@redhat.com>
> Cc: dev@openvswitch.org
> Sent: Monday, 12 June, 2017 6:44:43 PM
> Subject: Re: [ovs-dev] [PATCH] byte-order: avoid left shifts with unrepresentable results
> 
> On 06/12/2017 01:13 PM, Lance Richardson wrote:
> > A left shift that would produce a result that is not representable
> > by the type of the expression's result has "undefined behavior"
> > according to the C language standard. Avoid this by casting values
> > that could set the upper bit to unsigned types.
> >
> > Found via gcc's undefined behavior sanitizer.
> >
> > Signed-off-by: Lance Richardson <lrichard@redhat.com>
> > ---
> >   lib/byte-order.h | 4 ++--
> >   1 file changed, 2 insertions(+), 2 deletions(-)
> >
> > diff --git a/lib/byte-order.h b/lib/byte-order.h
> > index e864658..782439f 100644
> > --- a/lib/byte-order.h
> > +++ b/lib/byte-order.h
> > @@ -105,9 +105,9 @@ uint32_byteswap(uint32_t crc) {
> >       (OVS_FORCE ovs_be32)((uint32_t)(B1) << 16 | (B2))
> >   #else
> >   #define BYTES_TO_BE32(B1, B2, B3, B4) \
> > -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 8 | (B3) << 16 | (B4) <<
> > 24)
> > +    (OVS_FORCE ovs_be32)((B1) | (B2) << 8 | (B3) << 16 | (uint32_t)(B4) <<
> > 24)
> 
> if B2 is a unsigned char then what is the value of this expression?
> B2 << 8

The more interesting question would be "what is the type of this expression?".
It is "int" after integer promotions are done. The type of the expression
"(B2) << 8 | (uint32_t)(B4)" is uint32_t. If B2 is an unsigned char, all possible
values of B2 << 8 will fit.

> 
> Same here.  If B3 is an unsigned char what is the value of this expression?
> B3 << 16

Ditto.

> 
> >   #define BE16S_TO_BE32(B1, B2) \
> > -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 16)
> > +    (OVS_FORCE ovs_be32)((B1) | (uint32_t)(B2) << 16)
> >   #endif
> >
> >   /* These functions zero-extend big-endian values to longer ones,
> >
> I don't these macros.  There is no type checking so I think they could be
> improved.
> 
> I'd suggest turning them into inline functions so you get type checking, etc.
> 

I tend to agree, but those benefits are orthogonal to the goal of this patch,
which is simply to eliminate a case of undefined behavior that was detected
while running OVS unit tests with the undefined behavior sanitizer enabled. A
separate patch to convert these macros into inline functions would make sense,
IMO.

> My $0.02$
> 
> Thanks,
> 
> - Greg
> 

Thanks,

    Lance
Gregory Rose June 13, 2017, 1:35 a.m. UTC | #3
On 06/12/2017 04:36 PM, Lance Richardson wrote:
>
>
> ----- Original Message -----
> > From: "Greg Rose" <gvrose8192@gmail.com>
> > To: "Lance Richardson" <lrichard@redhat.com>
> > Cc: dev@openvswitch.org
> > Sent: Monday, 12 June, 2017 6:44:43 PM
> > Subject: Re: [ovs-dev] [PATCH] byte-order: avoid left shifts with unrepresentable results
> >
> > On 06/12/2017 01:13 PM, Lance Richardson wrote:
> >> A left shift that would produce a result that is not representable
> >> by the type of the expression's result has "undefined behavior"
> >> according to the C language standard. Avoid this by casting values
> >> that could set the upper bit to unsigned types.
> >>
> >> Found via gcc's undefined behavior sanitizer.
> >>
> >> Signed-off-by: Lance Richardson <lrichard@redhat.com>
> >> ---
> >>    lib/byte-order.h | 4 ++--
> >>    1 file changed, 2 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/lib/byte-order.h b/lib/byte-order.h
> >> index e864658..782439f 100644
> >> --- a/lib/byte-order.h
> >> +++ b/lib/byte-order.h
> >> @@ -105,9 +105,9 @@ uint32_byteswap(uint32_t crc) {
> >>        (OVS_FORCE ovs_be32)((uint32_t)(B1) << 16 | (B2))
> >>    #else
> >>    #define BYTES_TO_BE32(B1, B2, B3, B4) \
> >> -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 8 | (B3) << 16 | (B4) <<
> >> 24)
> >> +    (OVS_FORCE ovs_be32)((B1) | (B2) << 8 | (B3) << 16 | (uint32_t)(B4) <<
> >> 24)
> >
> > if B2 is a unsigned char then what is the value of this expression?
> > B2 << 8
>
> The more interesting question would be "what is the type of this expression?".
> It is "int" after integer promotions are done. The type of the expression
> "(B2) << 8 | (uint32_t)(B4)" is uint32_t. If B2 is an unsigned char, all possible
> values of B2 << 8 will fit.
>
> >
> > Same here.  If B3 is an unsigned char what is the value of this expression?
> > B3 << 16
>
> Ditto.
>
> >
> >>    #define BE16S_TO_BE32(B1, B2) \
> >> -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 16)
> >> +    (OVS_FORCE ovs_be32)((B1) | (uint32_t)(B2) << 16)
> >>    #endif
> >>
> >>    /* These functions zero-extend big-endian values to longer ones,
> >>
> > I don't these macros.  There is no type checking so I think they could be
> > improved.
> >
> > I'd suggest turning them into inline functions so you get type checking, etc.
> >
>
> I tend to agree, but those benefits are orthogonal to the goal of this patch,
> which is simply to eliminate a case of undefined behavior that was detected
> while running OVS unit tests with the undefined behavior sanitizer enabled. A
> separate patch to convert these macros into inline functions would make sense,
> IMO.
>
> > My $0.02$
> >
> > Thanks,
> >
> > - Greg
> >
>
> Thanks,
>
>      Lance
>
OK, I'm convinced.

Thanks!!!

Reviewed-by: Greg Rose <gvrose8192@gmail.com>
Ben Pfaff June 13, 2017, 4:51 a.m. UTC | #4
On Mon, Jun 12, 2017 at 07:36:48PM -0400, Lance Richardson wrote:
> 
> 
> ----- Original Message -----
> > From: "Greg Rose" <gvrose8192@gmail.com>
> > To: "Lance Richardson" <lrichard@redhat.com>
> > Cc: dev@openvswitch.org
> > Sent: Monday, 12 June, 2017 6:44:43 PM
> > Subject: Re: [ovs-dev] [PATCH] byte-order: avoid left shifts with unrepresentable results
> > 
> > On 06/12/2017 01:13 PM, Lance Richardson wrote:
> > > A left shift that would produce a result that is not representable
> > > by the type of the expression's result has "undefined behavior"
> > > according to the C language standard. Avoid this by casting values
> > > that could set the upper bit to unsigned types.
> > >
> > > Found via gcc's undefined behavior sanitizer.
> > >
> > > Signed-off-by: Lance Richardson <lrichard@redhat.com>
> > > ---
> > >   lib/byte-order.h | 4 ++--
> > >   1 file changed, 2 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/lib/byte-order.h b/lib/byte-order.h
> > > index e864658..782439f 100644
> > > --- a/lib/byte-order.h
> > > +++ b/lib/byte-order.h
> > > @@ -105,9 +105,9 @@ uint32_byteswap(uint32_t crc) {
> > >       (OVS_FORCE ovs_be32)((uint32_t)(B1) << 16 | (B2))
> > >   #else
> > >   #define BYTES_TO_BE32(B1, B2, B3, B4) \
> > > -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 8 | (B3) << 16 | (B4) <<
> > > 24)
> > > +    (OVS_FORCE ovs_be32)((B1) | (B2) << 8 | (B3) << 16 | (uint32_t)(B4) <<
> > > 24)
> > 
> > if B2 is a unsigned char then what is the value of this expression?
> > B2 << 8
> 
> The more interesting question would be "what is the type of this expression?".
> It is "int" after integer promotions are done. The type of the expression
> "(B2) << 8 | (uint32_t)(B4)" is uint32_t. If B2 is an unsigned char, all possible
> values of B2 << 8 will fit.
> 
> > 
> > Same here.  If B3 is an unsigned char what is the value of this expression?
> > B3 << 16
> 
> Ditto.
> 
> > 
> > >   #define BE16S_TO_BE32(B1, B2) \
> > > -    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 16)
> > > +    (OVS_FORCE ovs_be32)((B1) | (uint32_t)(B2) << 16)
> > >   #endif
> > >
> > >   /* These functions zero-extend big-endian values to longer ones,
> > >
> > I don't these macros.  There is no type checking so I think they could be
> > improved.
> > 
> > I'd suggest turning them into inline functions so you get type checking, etc.
> > 
> 
> I tend to agree, but those benefits are orthogonal to the goal of this patch,
> which is simply to eliminate a case of undefined behavior that was detected
> while running OVS unit tests with the undefined behavior sanitizer enabled. A
> separate patch to convert these macros into inline functions would make sense,
> IMO.

I don't see much downside to fixing all this in one go:
        https://patchwork.ozlabs.org/patch/774942/
diff mbox

Patch

diff --git a/lib/byte-order.h b/lib/byte-order.h
index e864658..782439f 100644
--- a/lib/byte-order.h
+++ b/lib/byte-order.h
@@ -105,9 +105,9 @@  uint32_byteswap(uint32_t crc) {
     (OVS_FORCE ovs_be32)((uint32_t)(B1) << 16 | (B2))
 #else
 #define BYTES_TO_BE32(B1, B2, B3, B4) \
-    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 8 | (B3) << 16 | (B4) << 24)
+    (OVS_FORCE ovs_be32)((B1) | (B2) << 8 | (B3) << 16 | (uint32_t)(B4) << 24)
 #define BE16S_TO_BE32(B1, B2) \
-    (OVS_FORCE ovs_be32)((uint32_t)(B1) | (B2) << 16)
+    (OVS_FORCE ovs_be32)((B1) | (uint32_t)(B2) << 16)
 #endif
 
 /* These functions zero-extend big-endian values to longer ones,