Download raw body.
dhcp6leased(8): stricter message validation
Here are three somewhat related diffs that make dhcp6leased(8)'s message
validation stricter by ensuring that the message we receive came from
the correct server and they are not spoofed.
In practice messages can still be spoofed and you have to trust the
layer 2 network between the DHCPv6 server and client, that's just always
true with these kinds of protocols.
OK?
commit 81adcdf355df90d1ab66cdf989afc95141bb6a7c
Author: Florian Obser <florian@narrans.de>
Date: Wed Jul 15 18:59:35 2026 +0200
Make sure the message belongs to the current transaction.
Pointed out by Clouditera Security.
diff --git engine.c engine.c
index 93976e53546..3c781991bb5 100644
--- engine.c
+++ engine.c
@@ -917,6 +917,11 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
dhcp_message_type2str(hdr.msg_type));
goto out;
}
+ if (memcmp(hdr.xid, iface->xid, XID_SIZE) != 0) {
+ log_debug("%s: ignoring %s with wrong transaction id",
+ __func__, dhcp_message_type2str(hdr.msg_type));
+ goto out;
+ }
iface->serverid_len = serverid_len;
memcpy(iface->serverid, serverid, SERVERID_SIZE);
memcpy(iface->pds, iface->new_pds, sizeof(iface->pds));
@@ -938,6 +943,11 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
dhcp_message_type2str(hdr.msg_type));
goto out;
}
+ if (memcmp(hdr.xid, iface->xid, XID_SIZE) != 0) {
+ log_debug("%s: ignoring %s with wrong transaction id",
+ __func__, dhcp_message_type2str(hdr.msg_type));
+ goto out;
+ }
iface->serverid_len = serverid_len;
memcpy(iface->serverid, serverid, SERVERID_SIZE);
commit 284af9abf7722be698209d46ca6eac192fc4ba6f
Author: Florian Obser <florian@narrans.de>
Date: Wed Jul 15 19:03:09 2026 +0200
Make sure we received a client identifier.
Pointed out by Clouditera Security.
diff --git engine.c engine.c
index 3c781991bb5..a656c706f70 100644
--- engine.c
+++ engine.c
@@ -732,6 +732,7 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
size_t rem;
uint32_t t1, t2, lease_time;
int serverid_len, rapid_commit = 0;
+ int found_client_id = 0;
uint8_t serverid[SERVERID_SIZE];
uint8_t *p;
char ifnamebuf[IF_NAMESIZE], *if_name;
@@ -793,6 +794,7 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
log_debug("%s: message not for us", __func__);
goto out;
}
+ found_client_id = 1;
break;
case DHO_SERVERID:
/*
@@ -870,6 +872,11 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
goto out;
}
+ if (!found_client_id) {
+ log_warnx("%s: Did not receive client identifier", __func__);
+ goto out;
+ }
+
SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
struct prefix *pd = &iface->new_pds[ia_conf->id];
commit a96595b66b2d3dc1c281a73ed1d5cf35e0f24e2c
Author: Florian Obser <florian@narrans.de>
Date: Wed Jul 15 19:38:11 2026 +0200
Make sure replies contain the expected server-id.
We only speak to and expect an answer from a specific server when we
are requesting or renewing a lease. In all other case we accept an
answer from any server.
Pointed out by Clouditera Security.
diff --git engine.c engine.c
index a656c706f70..80ed1cd1a31 100644
--- engine.c
+++ engine.c
@@ -754,6 +754,7 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
iface_conf->ia_count);
serverid_len = t1 = t2 = lease_time = 0;
+ memset(serverid, 0, SERVERID_SIZE);
memset(iface->new_pds, 0, sizeof(iface->new_pds));
p = dhcp->packet;
@@ -938,6 +939,15 @@ parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
switch (iface->state) {
case IF_REQUESTING:
case IF_RENEWING:
+ if (serverid_len == 0 || serverid_len !=
+ iface->serverid_len || memcmp(serverid,
+ iface->serverid, serverid_len) != 0) {
+ log_debug("%s: ignoring %s from wron server",
+ __func__, dhcp_message_type2str(
+ hdr.msg_type));
+ goto out;
+ }
+ break;
case IF_REBINDING:
case IF_REBOOTING:
break;
--
In my defence, I have been left unsupervised.
dhcp6leased(8): stricter message validation