grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v2 2/9] net: dhcp: replace parse_dhcp_vendor() with find_dhcp_opt


From: Andre Przywara
Subject: [PATCH v2 2/9] net: dhcp: replace parse_dhcp_vendor() with find_dhcp_option()
Date: Tue, 12 Feb 2019 17:46:53 +0000

From: Andrei Borzenkov <address@hidden>

For proper DHCP support we will need to parse DHCP options from a packet
more often and at various places.

Refactor the option parsing into a new function, which will scan a
packet to find *a particular* option field.
Use that new function in places where we were dealing with DHCP options
before.

Signed-off-by: Andre Przywara <address@hidden>
---
 grub-core/net/bootp.c | 285 ++++++++++++++++++++++++++----------------
 include/grub/net.h    |   1 +
 2 files changed, 176 insertions(+), 110 deletions(-)

diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c
index c92dfbd3a..c11038483 100644
--- a/grub-core/net/bootp.c
+++ b/grub-core/net/bootp.c
@@ -25,25 +25,50 @@
 #include <grub/net/udp.h>
 #include <grub/datetime.h>
 
-static void
-parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask)
+enum
 {
-  const grub_uint8_t *ptr, *ptr0;
+  GRUB_DHCP_OPT_OVERLOAD_FILE = 1,
+  GRUB_DHCP_OPT_OVERLOAD_SNAME = 2,
+};
 
-  ptr = ptr0 = vend;
+static const void *
+find_dhcp_option (const struct grub_net_bootp_packet *bp, grub_size_t size,
+                 grub_uint8_t opt_code, grub_uint8_t *opt_len)
+{
+  const grub_uint8_t *ptr;
+  grub_uint8_t overload = 0;
+  int end = 0;
+  grub_size_t i;
+
+  if (opt_len)
+    *opt_len = 0;
+
+  /* Is the packet big enough to hold at least the magic cookie? */
+  if (size < sizeof (*bp) + sizeof (grub_uint32_t))
+    goto out;
+
+  /*
+   * Pointer arithmetic to point behind the common stub packet, where
+   * the options start.
+   */
+  ptr = (grub_uint8_t *) (bp + 1);
 
   if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
       || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
       || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
       || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
-    return;
-  ptr = ptr + sizeof (grub_uint32_t);
-  while (ptr - ptr0 < limit)
+    goto out;
+
+  size -= sizeof (*bp);
+  i = sizeof (grub_uint32_t);
+
+again:
+  while (i < size)
     {
       grub_uint8_t tagtype;
       grub_uint8_t taglength;
 
-      tagtype = *ptr++;
+      tagtype = ptr[i++];
 
       /* Pad tag.  */
       if (tagtype == GRUB_NET_BOOTP_PAD)
@@ -51,81 +76,76 @@ parse_dhcp_vendor (const char *name, const void *vend, int 
limit, int *mask)
 
       /* End tag.  */
       if (tagtype == GRUB_NET_BOOTP_END)
-       return;
-
-      taglength = *ptr++;
-
-      switch (tagtype)
        {
-       case GRUB_NET_BOOTP_NETMASK:
-         if (taglength == 4)
-           {
-             int i;
-             for (i = 0; i < 32; i++)
-               if (!(ptr[i / 8] & (1 << (7 - (i % 8)))))
-                 break;
-             *mask = i;
-           }
+         end = 1;
          break;
+       }
 
-       case GRUB_NET_BOOTP_ROUTER:
-         if (taglength == 4)
-           {
-             grub_net_network_level_netaddress_t target;
-             grub_net_network_level_address_t gw;
-             char *rname;
-             
-             target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-             target.ipv4.base = 0;
-             target.ipv4.masksize = 0;
-             gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-             grub_memcpy (&gw.ipv4, ptr, sizeof (gw.ipv4));
-             rname = grub_xasprintf ("%s:default", name);
-             if (rname)
-               grub_net_add_route_gw (rname, target, gw, NULL);
-             grub_free (rname);
-           }
-         break;
-       case GRUB_NET_BOOTP_DNS:
-         {
-           int i;
-           for (i = 0; i < taglength / 4; i++)
-             {
-               struct grub_net_network_level_address s;
-               s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-               s.ipv4 = grub_get_unaligned32 (ptr);
-               s.option = DNS_OPTION_PREFER_IPV4;
-               grub_net_add_dns_server (&s);
-               ptr += 4;
-             }
-         }
-         continue;
-       case GRUB_NET_BOOTP_HOSTNAME:
-          grub_env_set_net_property (name, "hostname", (const char *) ptr,
-                                     taglength);
-          break;
-
-       case GRUB_NET_BOOTP_DOMAIN:
-          grub_env_set_net_property (name, "domain", (const char *) ptr,
-                                     taglength);
-          break;
-
-       case GRUB_NET_BOOTP_ROOT_PATH:
-          grub_env_set_net_property (name, "rootpath", (const char *) ptr,
-                                     taglength);
-          break;
-
-       case GRUB_NET_BOOTP_EXTENSIONS_PATH:
-          grub_env_set_net_property (name, "extensionspath", (const char *) 
ptr,
-                                     taglength);
-          break;
-
-         /* If you need any other options please contact GRUB
-            development team.  */
+      if (i >= size)
+       goto out;
+
+      taglength = ptr[i++];
+      if (i + taglength >= size)
+       goto out;
+
+      /* FIXME RFC 3396 options concatentation */
+      if (tagtype == opt_code)
+       {
+         if (opt_len)
+           *opt_len = taglength;
+         return &ptr[i];
        }
 
-      ptr += taglength;
+      if (tagtype == GRUB_NET_DHCP_OVERLOAD && taglength == 1)
+       overload = ptr[i];
+
+      i += taglength;
     }
+
+  if (!end)
+    return 0;
+
+  /* RFC2131, 4.1, 23ff:
+   * If the options in a DHCP message extend into the 'sname' and 'file'
+   * fields, the 'option overload' option MUST appear in the 'options'
+   * field, with value 1, 2 or 3, as specified in RFC 1533.  If the
+   * 'option overload' option is present in the 'options' field, the
+   * options in the 'options' field MUST be terminated by an 'end' option,
+   * and MAY contain one or more 'pad' options to fill the options field.
+   * The options in the 'sname' and 'file' fields (if in use as indicated
+   * by the 'options overload' option) MUST begin with the first octet of
+   * the field, MUST be terminated by an 'end' option, and MUST be
+   * followed by 'pad' options to fill the remainder of the field.  Any
+   * individual option in the 'options', 'sname' and 'file' fields MUST be
+   * entirely contained in that field.  The options in the 'options' field
+   * MUST be interpreted first, so that any 'option overload' options may
+   * be interpreted.  The 'file' field MUST be interpreted next (if the
+   * 'option overload' option indicates that the 'file' field contains
+   * DHCP options), followed by the 'sname' field.
+   *
+   * FIXME: We do not explicitly check for trailing 'pad' options here.
+   */
+  end = 0;
+  if (overload & GRUB_DHCP_OPT_OVERLOAD_FILE)
+  {
+    overload &= ~GRUB_DHCP_OPT_OVERLOAD_FILE;
+    ptr = (grub_uint8_t *) &bp->boot_file[0];
+    size = sizeof (bp->boot_file);
+    i = 0;
+    goto again;
+  }
+
+  if (overload & GRUB_DHCP_OPT_OVERLOAD_SNAME)
+  {
+    overload &= ~GRUB_DHCP_OPT_OVERLOAD_SNAME;
+    ptr = (grub_uint8_t *) &bp->server_name[0];
+    size = sizeof (bp->server_name);
+    i = 0;
+    goto again;
+  }
+
+out:
+  return 0;
 }
 
 #define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - 
(grub_uint8_t *)(y)))
@@ -143,6 +163,8 @@ grub_net_configure_by_dhcp_ack (const char *name,
   struct grub_net_network_level_interface *inter;
   int mask = -1;
   char server_ip[sizeof ("xxx.xxx.xxx.xxx")];
+  const grub_uint8_t *opt;
+  grub_uint8_t opt_len;
 
   addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
   addr.ipv4 = bp->your_ip;
@@ -225,9 +247,69 @@ grub_net_configure_by_dhcp_ack (const char *name,
            **path = 0;
        }
     }
-  if (size > OFFSET_OF (vendor, bp))
-    parse_dhcp_vendor (name, &bp->vendor, size - OFFSET_OF (vendor, bp), 
&mask);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_NETMASK, &opt_len);
+  if (opt && opt_len == 4)
+    {
+      int i;
+      for (i = 0; i < 32; i++)
+       if (!(opt[i / 8] & (1 << (7 - (i % 8)))))
+         break;
+      mask = i;
+    }
   grub_net_add_ipv4_local (inter, mask);
+
+  /* We do not implement dead gateway detection and the first entry SHOULD
+     be preferred one */
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_ROUTER, &opt_len);
+  if (opt && opt_len && !(opt_len & 3))
+    {
+      grub_net_network_level_netaddress_t target;
+      grub_net_network_level_address_t gw;
+      char *rname;
+
+      target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+      target.ipv4.base = 0;
+      target.ipv4.masksize = 0;
+      gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+      gw.ipv4 = grub_get_unaligned32 (opt);
+      rname = grub_xasprintf ("%s:default", name);
+      if (rname)
+       grub_net_add_route_gw (rname, target, gw, 0);
+      grub_free (rname);
+    }
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_DNS, &opt_len);
+  if (opt && opt_len && !(opt_len & 3))
+    {
+      int i;
+      for (i = 0; i < opt_len / 4; i++)
+       {
+         struct grub_net_network_level_address s;
+
+         s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+         s.ipv4 = grub_get_unaligned32 (opt);
+         s.option = DNS_OPTION_PREFER_IPV4;
+         grub_net_add_dns_server (&s);
+         opt += 4;
+       }
+    }
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_HOSTNAME, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "hostname", (const char *) opt, opt_len);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_DOMAIN, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "domain", (const char *) opt, opt_len);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_ROOT_PATH, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "rootpath", (const char *) opt, opt_len);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_EXTENSIONS_PATH, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "extensionspath", (const char *) opt, 
opt_len);
   
   inter->dhcp_ack = grub_malloc (size);
   if (inter->dhcp_ack)
@@ -286,8 +368,8 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ 
((unused)),
                  int argc, char **args)
 {
   struct grub_net_network_level_interface *inter;
-  int num;
-  grub_uint8_t *ptr;
+  unsigned num;
+  const grub_uint8_t *ptr;
   grub_uint8_t taglength;
 
   if (argc < 4)
@@ -305,44 +387,27 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ 
((unused)),
   if (!inter->dhcp_ack)
     return grub_error (GRUB_ERR_IO, N_("no DHCP info found"));
 
-  if (inter->dhcp_acklen <= OFFSET_OF (vendor, inter->dhcp_ack))
-    return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
-
-  num = grub_strtoul (args[2], 0, 0);
-  if (grub_errno)
-    return grub_errno;
-
   ptr = inter->dhcp_ack->vendor;
 
-  if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
+  /* This duplicates check in find_dhcp_option to preserve previous error 
return */
+  if (inter->dhcp_acklen < OFFSET_OF (vendor, inter->dhcp_ack) + sizeof 
(grub_uint32_t)
+      || ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
       || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
       || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
       || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
     return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
-  ptr = ptr + sizeof (grub_uint32_t);
-  while (1)
-    {
-      grub_uint8_t tagtype;
-
-      if (ptr >= ((grub_uint8_t *) inter->dhcp_ack) + inter->dhcp_acklen)
-       return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
-
-      tagtype = *ptr++;
 
-      /* Pad tag.  */
-      if (tagtype == 0)
-       continue;
+  num = grub_strtoul (args[2], 0, 0);
+  if (grub_errno)
+    return grub_errno;
 
-      /* End tag.  */
-      if (tagtype == 0xff)
-       return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
+  /* Exclude PAD (0) and END (255) option codes */
+  if (num == 0 || num > 254)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid DHCP option code"));
 
-      taglength = *ptr++;
-       
-      if (tagtype == num)
-       break;
-      ptr += taglength;
-    }
+  ptr = find_dhcp_option (inter->dhcp_ack, inter->dhcp_acklen, num, 
&taglength);
+  if (!ptr)
+    return grub_error (GRUB_ERR_IO, N_("no DHCP option %u found"), num);
 
   if (grub_strcmp (args[3], "string") == 0)
     {
diff --git a/include/grub/net.h b/include/grub/net.h
index 1096b2432..0c7286bd2 100644
--- a/include/grub/net.h
+++ b/include/grub/net.h
@@ -457,6 +457,7 @@ enum
     GRUB_NET_BOOTP_DOMAIN = 0x0f,
     GRUB_NET_BOOTP_ROOT_PATH = 0x11,
     GRUB_NET_BOOTP_EXTENSIONS_PATH = 0x12,
+    GRUB_NET_DHCP_OVERLOAD = 52,
     GRUB_NET_BOOTP_END = 0xff
   };
 
-- 
2.17.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]