[Openvpn-devel,v2] Fix unaligned access in macOS/Solaris hwaddr

Message ID 20230810140210.4047842-1-arne@rfc2549.org
State Superseded
Headers show
Series [Openvpn-devel,v2] Fix unaligned access in macOS/Solaris hwaddr | expand

Commit Message

Arne Schwabe Aug. 10, 2023, 2:02 p.m. UTC
The undefined behaviour USAN clang checker found this.

This fix is a bit messy but so are the original structures.

Patch v2: handle the fact we need to beyond the struct ifr
          correctly when mapping the result to struct sockaddr_dl

Change-Id: Ia797c8801fa9a9bc10b6674efde5fdbd7132e4a8
Signed-off-by: Arne Schwabe <arne@rfc2549.org>
---
 src/openvpn/route.c | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

Comments

Frank Lichtenheld Aug. 11, 2023, 9:56 a.m. UTC | #1
On Thu, Aug 10, 2023 at 04:02:10PM +0200, Arne Schwabe wrote:
> The undefined behaviour USAN clang checker found this.
> 
> This fix is a bit messy but so are the original structures.
> 
> Patch v2: handle the fact we need to beyond the struct ifr
>           correctly when mapping the result to struct sockaddr_dl
> 
> Change-Id: Ia797c8801fa9a9bc10b6674efde5fdbd7132e4a8
> Signed-off-by: Arne Schwabe <arne@rfc2549.org>
> ---
>  src/openvpn/route.c | 32 +++++++++++++++++++++++---------
>  1 file changed, 23 insertions(+), 9 deletions(-)
> 
> diff --git a/src/openvpn/route.c b/src/openvpn/route.c
> index 90e981e97..bcf6fb878 100644
> --- a/src/openvpn/route.c
> +++ b/src/openvpn/route.c
> @@ -3641,7 +3641,7 @@ get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
>      if (rgi->flags & RGI_IFACE_DEFINED)
>      {
>          struct ifconf ifc;
> -        struct ifreq *ifr;
> +        struct ifreq ifr;
>          const int bufsize = 4096;
>          char *buffer;
>  
> @@ -3666,23 +3666,37 @@ get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
>  
>          for (cp = buffer; cp <= buffer + ifc.ifc_len - sizeof(struct ifreq); )
>          {
> -            ifr = (struct ifreq *)cp;
> +            /* this is not always using an 8 byte alignment that struct ifr
> +             * requires */
> +            memcpy(&ifr, cp, sizeof(struct ifreq));
>  #if defined(TARGET_SOLARIS)
> -            const size_t len = sizeof(ifr->ifr_name) + sizeof(ifr->ifr_addr);
> +            const size_t len = sizeof(ifr.ifr_name) + sizeof(ifr.ifr_addr);
>  #else
> -            const size_t len = sizeof(ifr->ifr_name) + max(sizeof(ifr->ifr_addr), ifr->ifr_addr.sa_len);
> +            const size_t len = sizeof(ifr.ifr_name) + max(sizeof(ifr.ifr_addr), ifr.ifr_addr.sa_len);
>  #endif
>  
> -            if (!ifr->ifr_addr.sa_family)
> +            if (!ifr.ifr_addr.sa_family)
>              {
>                  break;
>              }
> -            if (!strncmp(ifr->ifr_name, rgi->iface, IFNAMSIZ))
> +            if (!strncmp(ifr.ifr_name, rgi->iface, IFNAMSIZ))
>              {
> -                if (ifr->ifr_addr.sa_family == AF_LINK)
> +                if (ifr.ifr_addr.sa_family == AF_LINK)
>                  {
> -                    struct sockaddr_dl *sdl = (struct sockaddr_dl *)&ifr->ifr_addr;
> -                    memcpy(rgi->hwaddr, LLADDR(sdl), 6);
> +                    /* This is a broken member access. struct sockaddr_dl has
> +                     * 20 bytes while if_addr has only 16 bytes. So casting if_addr
> +                     * to struct sockaddr_dl gives (legitimate) warnings
> +                     *
> +                     * sockaddr_dl has 12 bytes space for the inrerface name and

"interface"

> +                     * the hw address. So the last 4 that might be part of the
> +                     * hw address are not in if_addr, so we need

Sentence missing words at the end?

> +                     *
> +                     * So we use a memcpy here to avoid the warnings with ASAN
> +                     * that we are doing a very nasty cast here
> +                     */
> +                    struct sockaddr_dl sdl = { 0 };
> +                    memcpy(&sdl, cp + offsetof(struct ifreq, ifr_addr), sizeof(sdl));
> +                    memcpy(rgi->hwaddr, LLADDR(&sdl), 6);
>                      rgi->flags |= RGI_HWADDR_DEFINED;

So, reading the documentation about sockaddr and sockaddr_dl, I'm not sure that even
this code is safe. E.g. you say "sockaddr_dl has 12 bytes space", but is that actually true?
The actual headers always seems to suggest "at least 12 bytes space". Similar to how the generic
sockaddr has "at least 16 bytes size". So is that memcpy that you're doing actually guaranteed
to copy enough data to make LLADDR work? Or can LLADDR actually point behind
sizeof(sockaddr_dl)? The documentation and comments in the BSD headers seem to be vague
about that?

Regards,

Patch

diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index 90e981e97..bcf6fb878 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -3641,7 +3641,7 @@  get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
     if (rgi->flags & RGI_IFACE_DEFINED)
     {
         struct ifconf ifc;
-        struct ifreq *ifr;
+        struct ifreq ifr;
         const int bufsize = 4096;
         char *buffer;
 
@@ -3666,23 +3666,37 @@  get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
 
         for (cp = buffer; cp <= buffer + ifc.ifc_len - sizeof(struct ifreq); )
         {
-            ifr = (struct ifreq *)cp;
+            /* this is not always using an 8 byte alignment that struct ifr
+             * requires */
+            memcpy(&ifr, cp, sizeof(struct ifreq));
 #if defined(TARGET_SOLARIS)
-            const size_t len = sizeof(ifr->ifr_name) + sizeof(ifr->ifr_addr);
+            const size_t len = sizeof(ifr.ifr_name) + sizeof(ifr.ifr_addr);
 #else
-            const size_t len = sizeof(ifr->ifr_name) + max(sizeof(ifr->ifr_addr), ifr->ifr_addr.sa_len);
+            const size_t len = sizeof(ifr.ifr_name) + max(sizeof(ifr.ifr_addr), ifr.ifr_addr.sa_len);
 #endif
 
-            if (!ifr->ifr_addr.sa_family)
+            if (!ifr.ifr_addr.sa_family)
             {
                 break;
             }
-            if (!strncmp(ifr->ifr_name, rgi->iface, IFNAMSIZ))
+            if (!strncmp(ifr.ifr_name, rgi->iface, IFNAMSIZ))
             {
-                if (ifr->ifr_addr.sa_family == AF_LINK)
+                if (ifr.ifr_addr.sa_family == AF_LINK)
                 {
-                    struct sockaddr_dl *sdl = (struct sockaddr_dl *)&ifr->ifr_addr;
-                    memcpy(rgi->hwaddr, LLADDR(sdl), 6);
+                    /* This is a broken member access. struct sockaddr_dl has
+                     * 20 bytes while if_addr has only 16 bytes. So casting if_addr
+                     * to struct sockaddr_dl gives (legitimate) warnings
+                     *
+                     * sockaddr_dl has 12 bytes space for the inrerface name and
+                     * the hw address. So the last 4 that might be part of the
+                     * hw address are not in if_addr, so we need
+                     *
+                     * So we use a memcpy here to avoid the warnings with ASAN
+                     * that we are doing a very nasty cast here
+                     */
+                    struct sockaddr_dl sdl = { 0 };
+                    memcpy(&sdl, cp + offsetof(struct ifreq, ifr_addr), sizeof(sdl));
+                    memcpy(rgi->hwaddr, LLADDR(&sdl), 6);
                     rgi->flags |= RGI_HWADDR_DEFINED;
                 }
             }