untrusted comment: verify with openbsd-68-base.pub
RWQZj25CSG5R2l61cdgQ56038WHHKquXI+WJ7kBwDmm0mlSaqwSxaS7LkpOJJg2PAuLaVOFtLIMOGfVgZVKzjZkfrLIMawrmCAc=

OpenBSD 6.8 errata 025, June 9, 2021:

vmd guests could cause stack overflows by crafting malicious dhcp
requests when using local interfaces.

Apply by doing:
   signify -Vep /etc/signify/openbsd-68-base.pub -x 025_vmd.patch.sig \
       -m - | (cd /usr/src && patch -p0)

And then rebuild and install vmd(8):
   cd /usr/src/usr.sbin/vmd
   make obj
   make
   make install

Index: usr.sbin/vmd/dhcp.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/dhcp.c,v
diff -u -p -r1.8 dhcp.c
--- usr.sbin/vmd/dhcp.c 27 Dec 2018 19:51:30 -0000      1.8
+++ usr.sbin/vmd/dhcp.c 6 Jun 2021 17:04:22 -0000
@@ -21,6 +21,8 @@

#include <net/if.h>
#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>

@@ -34,6 +36,10 @@
#include "dhcp.h"
#include "virtio.h"

+#define OPTIONS_OFFSET offsetof(struct dhcp_packet, options)
+#define OPTIONS_MAX_LEN        \
+       (1500 - sizeof(struct ip) - sizeof(struct udphdr) - OPTIONS_OFFSET)
+
static const uint8_t broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
extern struct vmd *env;

@@ -41,7 +47,8 @@ ssize_t
dhcp_request(struct vionet_dev *dev, char *buf, size_t buflen, char **obuf)
{
       unsigned char           *respbuf = NULL, *op, *oe, dhcptype = 0;
-       ssize_t                  offset, respbuflen = 0;
+       unsigned char           *opts = NULL;
+       ssize_t                  offset, optslen, respbuflen = 0;
       struct packet_ctx        pc;
       struct dhcp_packet       req, resp;
       struct in_addr           server_addr, mask, client_addr, requested_addr;
@@ -50,7 +57,8 @@ dhcp_request(struct vionet_dev *dev, cha
       struct vmd_vm           *vm;
       const char              *hostname = NULL;

-       if (buflen < (ssize_t)(BOOTP_MIN_LEN + sizeof(struct ether_header)))
+       if (buflen < BOOTP_MIN_LEN + ETHER_HDR_LEN ||
+           buflen > 1500 + ETHER_HDR_LEN)
               return (-1);

       memset(&pc, 0, sizeof(pc));
@@ -68,8 +76,11 @@ dhcp_request(struct vionet_dev *dev, cha
           ntohs(ss2sin(&pc.pc_dst)->sin_port) != SERVER_PORT)
               return (-1);

+       /* Only populate the base DHCP fields. Options are parsed separately. */
+       if ((size_t)offset + OPTIONS_OFFSET > buflen)
+               return (-1);
       memset(&req, 0, sizeof(req));
-       memcpy(&req, buf + offset, buflen - offset);
+       memcpy(&req, buf + offset, OPTIONS_OFFSET);

       if (req.op != BOOTREQUEST ||
           req.htype != pc.pc_htype ||
@@ -81,27 +92,37 @@ dhcp_request(struct vionet_dev *dev, cha
       if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
               return (-1);

-       /* Get a few DHCP options (best effort as we fall back to BOOTP) */
-       if (memcmp(&req.options,
-           DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN) == 0) {
-               memset(&requested_addr, 0, sizeof(requested_addr));
-               op = req.options + DHCP_OPTIONS_COOKIE_LEN;
-               oe = req.options + sizeof(req.options);
-               while (*op != DHO_END && op < oe) {
-                       if (op[0] == DHO_PAD) {
-                               op++;
-                               continue;
+       /*
+        * If packet has data that could be DHCP options, check for the cookie
+        * and then see if the region is still long enough to contain at least
+        * one variable length option (3 bytes). If not, fallback to BOOTP.
+        */
+       optslen = buflen - offset - OPTIONS_OFFSET;
+       if (optslen > DHCP_OPTIONS_COOKIE_LEN + 3 &&
+           optslen < (ssize_t)OPTIONS_MAX_LEN) {
+               opts = buf + offset + OPTIONS_OFFSET;
+
+               if (memcmp(opts, DHCP_OPTIONS_COOKIE,
+                       DHCP_OPTIONS_COOKIE_LEN) == 0) {
+                       memset(&requested_addr, 0, sizeof(requested_addr));
+                       op = opts + DHCP_OPTIONS_COOKIE_LEN;
+                       oe = opts + optslen;
+                       while (*op != DHO_END && op + 1 < oe) {
+                               if (op[0] == DHO_PAD) {
+                                       op++;
+                                       continue;
+                               }
+                               if (op + 2 + op[1] > oe)
+                                       break;
+                               if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
+                                   op[1] == 1)
+                                       dhcptype = op[2];
+                               else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
+                                   op[1] == sizeof(requested_addr))
+                                       memcpy(&requested_addr, &op[2],
+                                           sizeof(requested_addr));
+                               op += 2 + op[1];
                       }
-                       if (op + 1 + op[1] >= oe)
-                               break;
-                       if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
-                           op[1] == 1)
-                               dhcptype = op[2];
-                       else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
-                           op[1] == sizeof(requested_addr))
-                               memcpy(&requested_addr, &op[2],
-                                   sizeof(requested_addr));
-                       op += 2 + op[1];
               }
       }

@@ -140,8 +161,7 @@ dhcp_request(struct vionet_dev *dev, cha
       if (*obuf != NULL)
               goto fail;

-       buflen = 0;
-       respbuflen = DHCP_MTU_MAX;
+       respbuflen = sizeof(resp);
       if ((respbuf = calloc(1, respbuflen)) == NULL)
               goto fail;

@@ -155,14 +175,9 @@ dhcp_request(struct vionet_dev *dev, cha
               goto fail;
       }

-       /* BOOTP uses a 64byte vendor field instead of the DHCP options */
-       resplen = BOOTP_MIN_LEN;
-
       /* Add BOOTP Vendor Extensions (DHCP options) */
-       o = 0;
-       memcpy(&resp.options,
-           DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
-       o+= DHCP_OPTIONS_COOKIE_LEN;
+       memcpy(&resp.options, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
+       o = DHCP_OPTIONS_COOKIE_LEN;

       /* Did we receive a DHCP request or was it just BOOTP? */
       if (dhcptype) {
@@ -213,8 +228,13 @@ dhcp_request(struct vionet_dev *dev, cha
       memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
       o += sizeof(server_addr);

-       if (hostname != NULL) {
-               len = strlen(hostname);
+       if (hostname != NULL && (len = strlen(hostname)) > 1) {
+               /* Check if there's still room for the option type and len (2),
+                * hostname, and a final to-be-added DHO_END (1). */
+               if (o + 2 + len + 1 > sizeof(resp.options)) {
+                       log_debug("%s: hostname too long", __func__);
+                       goto fail;
+               }
               resp.options[o++] = DHO_HOST_NAME;
               resp.options[o++] = len;
               memcpy(&resp.options[o], hostname, len);
@@ -223,7 +243,7 @@ dhcp_request(struct vionet_dev *dev, cha

       resp.options[o++] = DHO_END;

-       resplen = offsetof(struct dhcp_packet, options) + o;
+       resplen = OPTIONS_OFFSET + o;

       /* Minimum packet size */
       if (resplen < BOOTP_MIN_LEN)
@@ -242,5 +262,5 @@ dhcp_request(struct vionet_dev *dev, cha
       return (respbuflen);
 fail:
       free(respbuf);
-       return (0);
+       return (-1);
}