@@ -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"
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+