]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
ND: Enforce require and reject policy
authorRoy Marples <roy@marples.name>
Sat, 20 Jun 2026 20:31:15 +0000 (21:31 +0100)
committerGitHub <noreply@github.com>
Sat, 20 Jun 2026 20:31:15 +0000 (21:31 +0100)
Reported by NVIDIA Project Vanessa

src/ipv6nd.c

index c219bdb90bb16c4855b62e6e7bda16d04497c70f..f2c8c207420d8ef65e8cff6a7a529636ceaf0530 100644 (file)
@@ -949,7 +949,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from,
     const char *sfrom, struct interface *ifp, struct icmp6_hdr *icp, size_t len,
     int hoplimit)
 {
-       size_t i, olen;
+       size_t i, olen, rlen;
        struct nd_router_advert *nd_ra;
        struct nd_opt_hdr ndo;
        struct nd_opt_prefix_info pi;
@@ -957,12 +957,13 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from,
        struct nd_opt_rdnss rdnss;
        struct nd_opt_ri ri;
        struct routeinfo *rinfo;
+       struct if_options *ifo;
        uint8_t *p;
        struct ra *rap;
        struct in6_addr pi_prefix;
        struct ipv6_addr *ia;
        struct dhcp_opt *dho;
-       bool new_rap, new_data, has_address;
+       bool new_rap, new_data, has_address, has_opt;
        uint32_t old_lifetime;
        int ifmtu;
        int loglevel;
@@ -1041,6 +1042,80 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from,
 
        nd_ra = (struct nd_router_advert *)icp;
 
+       /* Validate */
+       loglevel = rap == NULL || rap->willexpire || !rap->isreachable ?
+           LOG_ERR :
+           LOG_DEBUG;
+       ifo = ifp->options;
+       rlen = len;
+       len -= sizeof(struct nd_router_advert);
+       p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
+       for (; len > 0; p += olen, len -= olen) {
+               if (len < sizeof(ndo)) {
+                       logmessage(loglevel, "%s: short RA option from %s",
+                           ifp->name, sfrom);
+                       break;
+               }
+               memcpy(&ndo, p, sizeof(ndo));
+               olen = (size_t)ndo.nd_opt_len * 8;
+               if (olen == 0) {
+                       /* RFC4681 4.6 says we MUST discard this ND packet. */
+                       logmessage(loglevel, "%s: zero length RA option %s",
+                           ifp->name, sfrom);
+                       return;
+               }
+               if (olen > len) {
+                       logmessage(loglevel,
+                           "%s: RA option length exceeds message from %s",
+                           ifp->name, sfrom);
+                       break;
+               }
+
+               if (has_option_mask(ifo->rejectmasknd, ndo.nd_opt_type)) {
+                       for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len;
+                           i++, dho++) {
+                               if (dho->option == ndo.nd_opt_type)
+                                       break;
+                       }
+                       if (i == ctx->nd_opts_len)
+                               logmessage(loglevel,
+                                   "%s: reject RA (option %d) from %s",
+                                   ifp->name, ndo.nd_opt_type, sfrom);
+                       else
+                               logmessage(loglevel,
+                                   "%s: reject RA (option %s) from %s",
+                                   ifp->name, dho->var, sfrom);
+                       return;
+               }
+       }
+
+       for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; i++, dho++) {
+               if (!has_option_mask(ifo->requiremasknd, dho->option))
+                       continue;
+               len = rlen;
+               len -= sizeof(struct nd_router_advert);
+               p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
+               has_opt = false;
+               for (; len > 0; p += olen, len -= olen) {
+                       if (len < sizeof(ndo))
+                               break;
+                       memcpy(&ndo, p, sizeof(ndo));
+                       olen = (size_t)ndo.nd_opt_len * 8;
+                       if (olen > len)
+                               break;
+                       if (ndo.nd_opt_type == dho->option) {
+                               has_opt = true;
+                               break;
+                       }
+               }
+               if (has_opt)
+                       continue;
+               logmessage(loglevel, "%s: reject RA (missing %s) from %s",
+                   ifp->name, dho->var, sfrom);
+               return;
+       }
+       len = rlen;
+
        /* We don't want to spam the log with the fact we got an RA every
         * 30 seconds or so, so only spam the log if it's different. */
        if (rap == NULL ||
@@ -1125,39 +1200,12 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from,
        len -= sizeof(struct nd_router_advert);
        p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
        for (; len > 0; p += olen, len -= olen) {
-               if (len < sizeof(ndo)) {
-                       logerrx("%s: short option", ifp->name);
+               if (len < sizeof(ndo))
                        break;
-               }
                memcpy(&ndo, p, sizeof(ndo));
                olen = (size_t)ndo.nd_opt_len * 8;
-               if (olen == 0) {
-                       /* RFC4681 4.6 says we MUST discard this ND packet. */
-                       logerrx("%s: zero length option", ifp->name);
-                       FREE_RAP(rap);
-                       return;
-               }
-               if (olen > len) {
-                       logerrx("%s: option length exceeds message", ifp->name);
+               if (olen > len)
                        break;
-               }
-
-               if (has_option_mask(ifp->options->rejectmasknd,
-                       ndo.nd_opt_type)) {
-                       for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len;
-                           i++, dho++) {
-                               if (dho->option == ndo.nd_opt_type)
-                                       break;
-                       }
-                       if (i == ctx->nd_opts_len)
-                               logwarnx("%s: reject RA (option %d) from %s",
-                                   ifp->name, ndo.nd_opt_type, rap->sfrom);
-                       else
-                               logwarnx("%s: reject RA (option %s) from %s",
-                                   ifp->name, dho->var, rap->sfrom);
-                       FREE_RAP(rap);
-                       return;
-               }
 
                if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type))
                        continue;
@@ -1406,15 +1454,6 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from,
                }
        }
 
-       for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; i++, dho++) {
-               if (has_option_mask(ifp->options->requiremasknd, dho->option)) {
-                       logwarnx("%s: reject RA (no option %s) from %s",
-                           ifp->name, dho->var, rap->sfrom);
-                       FREE_RAP(rap);
-                       return;
-               }
-       }
-
        TAILQ_FOREACH(ia, &rap->addrs, next) {
                if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0)
                        continue;