diff mbox series

[scarthgap,3/4] dhcpcd: patch CVE-2026-56116

Message ID 20260701104837.3577369-3-tgaige.opensource@witekio.com
State New
Headers show
Series [scarthgap,1/4] dhcpcd: patch CVE-2026-56113 | expand

Commit Message

tgaige.opensource@witekio.com July 1, 2026, 10:48 a.m. UTC
From: "Theo Gaige (Schneider Electric)" <tgaige.opensource@witekio.com>

Backport patch [1] mentionned in [2] and commit [3] which is dependency
of [1]

[1] https://github.com/NetworkConfiguration/dhcpcd/commit/708b4a56bae080a5b18c2e0c4c6fbe103131a2b0

[2] https://security-tracker.debian.org/tracker/CVE-2026-56116

[3] https://github.com/NetworkConfiguration/dhcpcd/commit/f1cf924ad691bc1e6bf33013407fbf838fa40fbe

Signed-off-by: Theo Gaige (Schneider Electric) <tgaige.opensource@witekio.com>
---
 .../dhcpcd/dhcpcd_10.0.6.bb                   |   2 +
 .../dhcpcd/files/CVE-2026-56116-pre.patch     | 482 ++++++++++++++++++
 .../dhcpcd/files/CVE-2026-56116.patch         |  31 ++
 3 files changed, 515 insertions(+)
 create mode 100644 meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116-pre.patch
 create mode 100644 meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116.patch
diff mbox series

Patch

diff --git a/meta/recipes-connectivity/dhcpcd/dhcpcd_10.0.6.bb b/meta/recipes-connectivity/dhcpcd/dhcpcd_10.0.6.bb
index bc87b91503..4a031cefea 100644
--- a/meta/recipes-connectivity/dhcpcd/dhcpcd_10.0.6.bb
+++ b/meta/recipes-connectivity/dhcpcd/dhcpcd_10.0.6.bb
@@ -17,6 +17,8 @@  SRC_URI = "git://github.com/NetworkConfiguration/dhcpcd;protocol=https;branch=ma
            file://0001-dhcpcd.8-Fix-conflict-error-when-enable-multilib.patch \
            file://CVE-2026-56113.patch \
            file://CVE-2026-56114.patch \
+           file://CVE-2026-56116-pre.patch \
+           file://CVE-2026-56116.patch \
            "
 
 SRCREV = "1c8ae59836fa87b4c63c598087f0460ec20ed862"
diff --git a/meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116-pre.patch b/meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116-pre.patch
new file mode 100644
index 0000000000..f9309de80a
--- /dev/null
+++ b/meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116-pre.patch
@@ -0,0 +1,482 @@ 
+From b13d64e33c4b02c4806857c49e4ccaa64b6015de Mon Sep 17 00:00:00 2001
+From: Daniel Goertzen <daniel.goertzen@gmail.com>
+Date: Fri, 8 Mar 2024 19:27:57 -0600
+Subject: [PATCH 1/2] add RFC4191 support (#297)
+
+* add RFC4191 support
+
+- handles route information options from RAs.
+- refactor `sa_fromprefix()` to expose lower level functionality
+- refactor `ipv6nd_rtprefix()` to be usable outside of `struct ra` context
+
+* changes as requested by RM
+
+- mostly minor/cosmetic changes
+- functional change: "no longer a default router" warning moved to capture changes from routeinfo options
+
+* simplify routeinfo_find/new
+
+(cherry picked from commit f1cf924ad691bc1e6bf33013407fbf838fa40fbe)
+
+This commit is a dependency of commit 708b4a56bae080a5b18c2e0c4c6fbe103131a2b0.
+
+Upstream-Status: Backport [https://github.com/NetworkConfiguration/dhcpcd/commit/f1cf924ad691bc1e6bf33013407fbf838fa40fbe]
+Signed-off-by: Theo Gaige (Schneider Electric) <tgaige.opensource@witekio.com>
+---
+ src/ipv6.c   |  29 ++++++++++-
+ src/ipv6nd.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++----
+ src/ipv6nd.h |  19 +++++++-
+ src/sa.c     |  46 ++++++++++++------
+ src/sa.h     |   1 +
+ 5 files changed, 201 insertions(+), 29 deletions(-)
+
+diff --git a/src/ipv6.c b/src/ipv6.c
+index eb8c617a..ce985d4e 100644
+--- a/src/ipv6.c
++++ b/src/ipv6.c
+@@ -2318,7 +2318,9 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
+ {
+ 	struct rt *rt;
+ 	struct ra *rap;
++	const struct routeinfo *rinfo;
+ 	const struct ipv6_addr *addr;
++	struct in6_addr netmask;
+ 
+ 	if (ctx->ra_routers == NULL)
+ 		return 0;
+@@ -2326,6 +2328,27 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
+ 	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ 		if (rap->expired)
+ 			continue;
++
++		/* add rfc4191 route information routes */
++		TAILQ_FOREACH (rinfo, &rap->rinfos, next) {
++			if(rinfo->lifetime == 0)
++				continue;
++			if ((rt = inet6_makeroute(rap->iface, rap)) == NULL)
++				continue;
++
++			in6_addr_fromprefix(&netmask, rinfo->prefix_len);
++
++			sa_in6_init(&rt->rt_dest, &rinfo->prefix);
++			sa_in6_init(&rt->rt_netmask, &netmask);
++			sa_in6_init(&rt->rt_gateway, &rap->from);
++#ifdef HAVE_ROUTE_PREF
++			rt->rt_pref = ipv6nd_rtpref(rinfo->flags);
++#endif
++
++			rt_proto_add(routes, rt);
++		}
++
++		/* add subnet routes */
+ 		TAILQ_FOREACH(addr, &rap->addrs, next) {
+ 			if (addr->prefix_vltime == 0)
+ 				continue;
+@@ -2333,11 +2356,13 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
+ 			if (rt) {
+ 				rt->rt_dflags |= RTDF_RA;
+ #ifdef HAVE_ROUTE_PREF
+-				rt->rt_pref = ipv6nd_rtpref(rap);
++				rt->rt_pref = ipv6nd_rtpref(rap->flags);
+ #endif
+ 				rt_proto_add(routes, rt);
+ 			}
+ 		}
++
++		/* add default route */
+ 		if (rap->lifetime == 0)
+ 			continue;
+ 		if (ipv6_anyglobal(rap->iface) == NULL)
+@@ -2347,7 +2372,7 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
+ 			continue;
+ 		rt->rt_dflags |= RTDF_RA;
+ #ifdef HAVE_ROUTE_PREF
+-		rt->rt_pref = ipv6nd_rtpref(rap);
++		rt->rt_pref = ipv6nd_rtpref(rap->flags);
+ #endif
+ 		rt_proto_add(routes, rt);
+ 	}
+diff --git a/src/ipv6nd.c b/src/ipv6nd.c
+index 9bf7c5df..9264dce7 100644
+--- a/src/ipv6nd.c
++++ b/src/ipv6nd.c
+@@ -71,6 +71,20 @@
+ #define	ND_OPT_PI_FLAG_ROUTER	0x20	/* Router flag in PI */
+ #endif
+ 
++#ifndef ND_OPT_RI
++#define ND_OPT_RI	24
++struct nd_opt_ri {		/* Route Information option RFC4191 */
++	uint8_t	 nd_opt_ri_type;
++	uint8_t	 nd_opt_ri_len;
++	uint8_t	 nd_opt_ri_prefixlen;
++	uint8_t	 nd_opt_ri_flags_reserved;
++	uint32_t nd_opt_ri_lifetime;
++	struct in6_addr nd_opt_ri_prefix;
++};
++__CTASSERT(sizeof(struct nd_opt_ri) == 24);
++#define OPT_RI_FLAG_PREFERENCE(flags) ((flags & 0x18) >> 3)
++#endif
++
+ #ifndef ND_OPT_RDNSS
+ #define ND_OPT_RDNSS			25
+ struct nd_opt_rdnss {           /* RDNSS option RFC 6106 */
+@@ -132,6 +146,8 @@ __CTASSERT(sizeof(struct nd_opt_dnssl) == 8);
+ //
+ 
+ static void ipv6nd_handledata(void *, unsigned short);
++static struct routeinfo *routeinfo_findalloc(struct ra *, const struct in6_addr *, uint8_t);
++static void routeinfohead_free(struct routeinfohead *);
+ 
+ /*
+  * Android ships buggy ICMP6 filter headers.
+@@ -612,10 +628,10 @@ ipv6nd_startexpire(struct interface *ifp)
+ }
+ 
+ int
+-ipv6nd_rtpref(struct ra *rap)
++ipv6nd_rtpref(uint8_t flags)
+ {
+ 
+-	switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) {
++	switch (flags & ND_RA_FLAG_RTPREF_MASK) {
+ 	case ND_RA_FLAG_RTPREF_HIGH:
+ 		return RTPREF_HIGH;
+ 	case ND_RA_FLAG_RTPREF_MEDIUM:
+@@ -624,7 +640,7 @@ ipv6nd_rtpref(struct ra *rap)
+ 	case ND_RA_FLAG_RTPREF_LOW:
+ 		return RTPREF_LOW;
+ 	default:
+-		logerrx("%s: impossible RA flag %x", __func__, rap->flags);
++		logerrx("%s: impossible RA flag %x", __func__, flags);
+ 		return RTPREF_INVALID;
+ 	}
+ 	/* NOTREACHED */
+@@ -649,7 +665,7 @@ ipv6nd_sortrouters(struct dhcpcd_ctx *ctx)
+ 				continue;
+ 			if (!ra1->isreachable && ra2->reachable)
+ 				continue;
+-			if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2))
++			if (ipv6nd_rtpref(ra1->flags) <= ipv6nd_rtpref(ra2->flags))
+ 				continue;
+ 			/* All things being equal, prefer older routers. */
+ 			/* We don't need to check time, becase newer
+@@ -827,6 +843,7 @@ ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra)
+ 	if (remove_ra)
+ 		TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next);
+ 	ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL);
++	routeinfohead_free(&rap->rinfos);
+ 	free(rap->data);
+ 	free(rap);
+ }
+@@ -1105,6 +1122,8 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
+ 	struct nd_opt_prefix_info pi;
+ 	struct nd_opt_mtu mtu;
+ 	struct nd_opt_rdnss rdnss;
++	struct nd_opt_ri ri;
++	struct routeinfo *rinfo;
+ 	uint8_t *p;
+ 	struct ra *rap;
+ 	struct in6_addr pi_prefix;
+@@ -1206,6 +1225,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
+ 		rap->from = from->sin6_addr;
+ 		strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
+ 		TAILQ_INIT(&rap->addrs);
++		TAILQ_INIT(&rap->rinfos);
+ 		new_rap = true;
+ 		rap->isreachable = true;
+ 	} else
+@@ -1237,9 +1257,6 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
+ 	rap->flags = nd_ra->nd_ra_flags_reserved;
+ 	old_lifetime = rap->lifetime;
+ 	rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
+-	if (!new_rap && rap->lifetime == 0 && old_lifetime != 0)
+-		logwarnx("%s: %s: no longer a default router (lifetime = 0)",
+-		    ifp->name, rap->sfrom);
+ 	if (nd_ra->nd_ra_curhoplimit != 0)
+ 		rap->hoplimit = nd_ra->nd_ra_curhoplimit;
+ 	else
+@@ -1502,6 +1519,46 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
+ 			    rdnss.nd_opt_rdnss_len > 1)
+ 				rap->hasdns = 1;
+ 			break;
++		case ND_OPT_RI:
++			if (ndo.nd_opt_len > 3) {
++				logmessage(loglevel, "%s: invalid route info option",
++				    ifp->name);
++				break;
++			}
++			memset(&ri, 0, sizeof(ri));
++			memcpy(&ri, p, olen); /* may be smaller than sizeof(ri), pad with zero */
++			if(ri.nd_opt_ri_prefixlen > 128) {
++				logmessage(loglevel, "%s: invalid route info prefix length",
++				    ifp->name);
++				break;
++			}
++
++			/* rfc4191 3.1 - RI for ::/0 applies to default route */
++			if(ri.nd_opt_ri_prefixlen == 0) {
++				rap->lifetime = ntohl(ri.nd_opt_ri_lifetime);
++
++				/* Update preference leaving other flags intact */
++				rap->flags = ((rap->flags & (~ (unsigned int)ND_RA_FLAG_RTPREF_MASK))
++					| ri.nd_opt_ri_flags_reserved) & 0xff;
++
++				break;
++			}
++
++			/* Update existing route info instead of rebuilding all routes so that
++			previously announced but now absent routes can stay alive.  To kill a
++			route early, an RI with lifetime=0 needs to be received (rfc4191 3.1)*/
++			rinfo = routeinfo_findalloc(rap, &ri.nd_opt_ri_prefix, ri.nd_opt_ri_prefixlen);
++			if(rinfo == NULL) {
++				logerr(__func__);
++				break;
++			}
++
++			/* Update/initialize other route info params */
++			rinfo->flags = ri.nd_opt_ri_flags_reserved;
++			rinfo->lifetime = ntohl(ri.nd_opt_ri_lifetime);
++			rinfo->acquired = rap->acquired;
++
++			break;
+ 		default:
+ 			continue;
+ 		}
+@@ -1537,6 +1594,10 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
+ 		ia->prefix_pltime = 0;
+ 	}
+ 
++	if (!new_rap && rap->lifetime == 0 && old_lifetime != 0)
++		logwarnx("%s: %s: no longer a default router (lifetime = 0)",
++		    ifp->name, rap->sfrom);
++
+ 	if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp))
+ 		logwarnx("%s: no global addresses for default route",
+ 		    ifp->name);
+@@ -1699,7 +1760,7 @@ ipv6nd_env(FILE *fp, const struct interface *ifp)
+ 			return -1;
+ 		if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1)
+ 			return -1;
+-		pref = ipv6nd_rtpref(rap);
++		pref = ipv6nd_rtpref(rap->flags);
+ 		if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix,
+ 		    rap->flags & ND_RA_FLAG_MANAGED    ? "M" : "",
+ 		    rap->flags & ND_RA_FLAG_OTHER      ? "O" : "",
+@@ -1804,6 +1865,7 @@ ipv6nd_expirera(void *arg)
+ 	uint32_t elapsed;
+ 	bool expired, valid;
+ 	struct ipv6_addr *ia;
++	struct routeinfo *rinfo, *rinfob;
+ 	size_t len, olen;
+ 	uint8_t *p;
+ 	struct nd_opt_hdr ndo;
+@@ -1823,7 +1885,8 @@ ipv6nd_expirera(void *arg)
+ 		if (rap->iface != ifp || rap->expired)
+ 			continue;
+ 		valid = false;
+-		if (rap->lifetime) {
++		/* lifetime may be set to infinite by rfc4191 route information */
++		if (rap->lifetime && rap->lifetime != ND6_INFINITE_LIFETIME) {
+ 			elapsed = (uint32_t)eloop_timespec_diff(&now,
+ 			    &rap->acquired, NULL);
+ 			if (elapsed >= rap->lifetime || rap->doexpire) {
+@@ -1879,6 +1942,20 @@ ipv6nd_expirera(void *arg)
+ 			}
+ 		}
+ 
++		/* Expire route information */
++		TAILQ_FOREACH_SAFE(rinfo, &rap->rinfos, next, rinfob) {
++			if (rinfo->lifetime == ND6_INFINITE_LIFETIME &&
++			    !rap->doexpire)
++				continue;
++			elapsed = (uint32_t)eloop_timespec_diff(&now,
++			    &rinfo->acquired, NULL);
++			if (elapsed >= rinfo->lifetime || rap->doexpire) {
++				logwarnx("%s: expired route %s",
++				    rap->iface->name, rinfo->sprefix);
++				TAILQ_REMOVE(&rap->rinfos, rinfo, next);
++			}
++		}
++
+ 		/* Work out expiry for ND options */
+ 		elapsed = (uint32_t)eloop_timespec_diff(&now,
+ 		    &rap->acquired, NULL);
+@@ -2135,3 +2212,43 @@ ipv6nd_startrs(struct interface *ifp)
+ 	eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp);
+ 	return;
+ }
++
++static struct routeinfo *routeinfo_findalloc(struct ra *rap, const struct in6_addr *prefix, uint8_t prefix_len)
++{
++	struct routeinfo *ri;
++	char buf[INET6_ADDRSTRLEN];
++	const char *p;
++
++	TAILQ_FOREACH(ri, &rap->rinfos, next) {
++		if (ri->prefix_len == prefix_len &&
++		    IN6_ARE_ADDR_EQUAL(&ri->prefix, prefix))
++			return ri;
++	}
++
++	ri = malloc(sizeof(struct routeinfo));
++	if (ri == NULL)
++		return NULL;
++
++	memcpy(&ri->prefix, prefix, sizeof(ri->prefix));
++	ri->prefix_len = prefix_len;
++	p = inet_ntop(AF_INET6, prefix, buf, sizeof(buf));
++	if (p)
++		snprintf(ri->sprefix,
++			sizeof(ri->sprefix),
++			"%s/%d",
++			p, prefix_len);
++	else
++		ri->sprefix[0] = '\0';
++	TAILQ_INSERT_TAIL(&rap->rinfos, ri, next);
++	return ri;
++}
++
++static void routeinfohead_free(struct routeinfohead *head)
++{
++	struct routeinfo *ri;
++
++	while ((ri = TAILQ_FIRST(head))) {
++		TAILQ_REMOVE(head, ri, next);
++		free(ri);
++	}
++}
+diff --git a/src/ipv6nd.h b/src/ipv6nd.h
+index b702c3bd..837b7d0f 100644
+--- a/src/ipv6nd.h
++++ b/src/ipv6nd.h
+@@ -37,6 +37,20 @@
+ #include "dhcpcd.h"
+ #include "ipv6.h"
+ 
++/* rfc4191 */
++struct routeinfo {
++	TAILQ_ENTRY(routeinfo) next;
++	struct in6_addr prefix;
++	uint8_t prefix_len;
++	uint32_t lifetime;
++	uint8_t flags;
++	struct timespec acquired;
++	char sprefix[INET6_ADDRSTRLEN];
++};
++
++TAILQ_HEAD(routeinfohead, routeinfo);
++
++
+ struct ra {
+ 	TAILQ_ENTRY(ra) next;
+ 	struct interface *iface;
+@@ -45,13 +59,14 @@ struct ra {
+ 	uint8_t *data;
+ 	size_t data_len;
+ 	struct timespec acquired;
+-	unsigned char flags;
++	uint8_t flags;
+ 	uint32_t lifetime;
+ 	uint32_t reachable;
+ 	uint32_t retrans;
+ 	uint32_t mtu;
+ 	uint8_t hoplimit;
+ 	struct ipv6_addrhead addrs;
++	struct routeinfohead rinfos;
+ 	bool hasdns;
+ 	bool expired;
+ 	bool willexpire;
+@@ -105,7 +120,7 @@ int ipv6nd_open(bool);
+ int ipv6nd_openif(struct interface *);
+ #endif
+ void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *);
+-int ipv6nd_rtpref(struct ra *);
++int ipv6nd_rtpref(uint8_t);
+ void ipv6nd_printoptions(const struct dhcpcd_ctx *,
+     const struct dhcp_opt *, size_t);
+ void ipv6nd_startrs(struct interface *);
+diff --git a/src/sa.c b/src/sa.c
+index f1e2e16e..05009d3b 100644
+--- a/src/sa.c
++++ b/src/sa.c
+@@ -300,11 +300,39 @@ sa_toprefix(const struct sockaddr *sa)
+ 	return prefix;
+ }
+ 
++static void
++ipbytes_fromprefix(uint8_t *ap, int prefix, int max_prefix)
++{
++	int bytes, bits, i;
++
++	bytes = prefix / NBBY;
++	bits = prefix % NBBY;
++
++	for (i = 0; i < bytes; i++)
++		*ap++ = 0xff;
++	if (bits) {
++		uint8_t a;
++
++		a = 0xff;
++		a  = (uint8_t)(a << (8 - bits));
++		*ap++ = a;
++	}
++	bytes = (max_prefix - prefix) / NBBY;
++	for (i = 0; i < bytes; i++)
++		*ap++ = 0x00;
++}
++
++void
++in6_addr_fromprefix(struct in6_addr *addr, int prefix)
++{
++	ipbytes_fromprefix((uint8_t *)addr, prefix, 128);
++}
++
+ int
+ sa_fromprefix(struct sockaddr *sa, int prefix)
+ {
+ 	uint8_t *ap;
+-	int max_prefix, bytes, bits, i;
++	int max_prefix;
+ 
+ 	switch (sa->sa_family) {
+ #ifdef INET
+@@ -328,22 +356,8 @@ sa_fromprefix(struct sockaddr *sa, int prefix)
+ 		return -1;
+ 	}
+ 
+-	bytes = prefix / NBBY;
+-	bits = prefix % NBBY;
+-
+ 	ap = (uint8_t *)sa + sa_addroffset(sa);
+-	for (i = 0; i < bytes; i++)
+-		*ap++ = 0xff;
+-	if (bits) {
+-		uint8_t a;
+-
+-		a = 0xff;
+-		a  = (uint8_t)(a << (8 - bits));
+-		*ap++ = a;
+-	}
+-	bytes = (max_prefix - prefix) / NBBY;
+-	for (i = 0; i < bytes; i++)
+-		*ap++ = 0x00;
++	ipbytes_fromprefix(ap, prefix, max_prefix);
+ 
+ #ifndef NDEBUG
+ 	/* Ensure the calculation is correct */
+diff --git a/src/sa.h b/src/sa.h
+index a848defd..902229af 100644
+--- a/src/sa.h
++++ b/src/sa.h
+@@ -67,6 +67,7 @@ bool sa_is_loopback(const struct sockaddr *);
+ void *sa_toaddr(struct sockaddr *);
+ int sa_toprefix(const struct sockaddr *);
+ int sa_fromprefix(struct sockaddr *, int);
++void in6_addr_fromprefix(struct in6_addr *, int);
+ const char *sa_addrtop(const struct sockaddr *, char *, socklen_t);
+ int sa_cmp(const struct sockaddr *, const struct sockaddr *);
+ void sa_in_init(struct sockaddr *, const struct in_addr *);
+-- 
+2.43.0
+
diff --git a/meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116.patch b/meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116.patch
new file mode 100644
index 0000000000..52d4eb5aa7
--- /dev/null
+++ b/meta/recipes-connectivity/dhcpcd/files/CVE-2026-56116.patch
@@ -0,0 +1,31 @@ 
+From 6d1a9c5118dc7910667888ef69c3f79379f427ee Mon Sep 17 00:00:00 2001
+From: Roy Marples <roy@marples.name>
+Date: Tue, 23 Jun 2026 00:34:58 +0100
+Subject: [PATCH 2/2] IPv6ND: Free routeinfo when it expires (#670)
+
+Reported-by: CuB3y0nd <root@cubeyond.net>
+
+(cherry picked from commit 708b4a56bae080a5b18c2e0c4c6fbe103131a2b0)
+
+CVE: CVE-2026-56116
+Upstream-Status: Backport [https://github.com/NetworkConfiguration/dhcpcd/commit/708b4a56bae080a5b18c2e0c4c6fbe103131a2b0]
+Signed-off-by: Theo Gaige (Schneider Electric) <tgaige.opensource@witekio.com>
+---
+ src/ipv6nd.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/ipv6nd.c b/src/ipv6nd.c
+index 9264dce7..75655ab1 100644
+--- a/src/ipv6nd.c
++++ b/src/ipv6nd.c
+@@ -1953,6 +1953,7 @@ ipv6nd_expirera(void *arg)
+ 				logwarnx("%s: expired route %s",
+ 				    rap->iface->name, rinfo->sprefix);
+ 				TAILQ_REMOVE(&rap->rinfos, rinfo, next);
++				free(rinfo);
+ 			}
+ 		}
+ 
+-- 
+2.43.0
+