sd-ndisc: better validate RA packets
authorTom Gundersen <teg@jklm.no>
Mon, 23 Nov 2015 14:59:58 +0000 (15:59 +0100)
committerTom Gundersen <teg@jklm.no>
Wed, 25 Nov 2015 17:30:31 +0000 (18:30 +0100)
Verify the hoplimit and that the received packet is large enough for the RA
header.

See <http://tools.ietf.org/html/rfc4861#section-6.1.2>.

src/libsystemd-network/icmp6-util.c
src/libsystemd-network/sd-ndisc.c

index 03505fc..acad9d7 100644 (file)
@@ -47,17 +47,15 @@ int icmp6_bind_router_solicitation(int index) {
                 .ipv6mr_interface = index,
         };
         _cleanup_close_ int s = -1;
-        int r, zero = 0, hops = 255;
+        int r, zero = 0, one = 1, hops = 255;
 
-        s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
-                   IPPROTO_ICMPV6);
+        s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
         if (s < 0)
                 return -errno;
 
         ICMP6_FILTER_SETBLOCKALL(&filter);
         ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
-        r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
-                       sizeof(filter));
+        r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
         if (r < 0)
                 return -errno;
 
@@ -65,23 +63,23 @@ int icmp6_bind_router_solicitation(int index) {
            IPV6_PKTINFO socket option also applies for ICMPv6 multicast.
            Empirical experiments indicates otherwise and therefore an
            IPV6_MULTICAST_IF socket option is used here instead */
-        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index,
-                       sizeof(index));
+        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index));
         if (r < 0)
                 return -errno;
 
-        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero,
-                       sizeof(zero));
+        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero));
         if (r < 0)
                 return -errno;
 
-        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,
-                       sizeof(hops));
+        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops));
         if (r < 0)
                 return -errno;
 
-        r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq,
-                       sizeof(mreq));
+        r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+        if (r < 0)
+                return -errno;
+
+        r = setsockopt(s, SOL_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
         if (r < 0)
                 return -errno;
 
index 249586f..f2bce3b 100644 (file)
@@ -418,8 +418,7 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
         return 0;
 }
 
-static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra,
-                          ssize_t len) {
+static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, ssize_t len) {
         void *opt;
         struct nd_opt_hdr *opt_hdr;
 
@@ -482,12 +481,25 @@ static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra,
 static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
         _cleanup_free_ struct nd_router_advert *ra = NULL;
         sd_ndisc *nd = userdata;
-        int r, buflen = 0, pref, stateful;
-        union sockaddr_union router = {};
-        socklen_t router_len = sizeof(router);
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_LEN(sizeof(int))];
+        } control = {};
+        struct iovec iov = {};
+        union sockaddr_union sa = {};
+        struct msghdr msg = {
+                .msg_name = &sa.sa,
+                .msg_namelen = sizeof(sa),
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        struct cmsghdr *cmsg;
         struct in6_addr *gw;
         unsigned lifetime;
         ssize_t len;
+        int r, pref, stateful, buflen = 0;
 
         assert(s);
         assert(nd);
@@ -500,24 +512,48 @@ static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t r
                 /* This really should not happen */
                 return -EIO;
 
-        ra = malloc(buflen);
+        iov.iov_len = buflen;
+
+        ra = malloc(iov.iov_len);
         if (!ra)
                 return -ENOMEM;
 
-        len = recvfrom(fd, ra, buflen, 0, &router.sa, &router_len);
+        iov.iov_base = ra;
+
+        len = recvmsg(fd, &msg, 0);
         if (len < 0) {
                 if (errno == EAGAIN || errno == EINTR)
                         return 0;
 
                 log_ndisc(nd, "Could not receive message from ICMPv6 socket: %m");
                 return -errno;
-        } else if (router_len == 0)
+        } else if ((size_t)len < sizeof(struct nd_router_advert)) {
+                return 0;
+        } else if (msg.msg_namelen == 0)
                 gw = NULL; /* only happens when running the test-suite over a socketpair */
-        else if (router_len != sizeof(router.in6)) {
-                log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)router_len);
+        else if (msg.msg_namelen != sizeof(sa.in6)) {
+                log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
                 return 0;
         } else
-                gw = &router.in6.sin6_addr;
+                gw = &sa.in6.sin6_addr;
+
+        assert(!(msg.msg_flags & MSG_CTRUNC));
+        assert(!(msg.msg_flags & MSG_TRUNC));
+
+        CMSG_FOREACH(cmsg, &msg) {
+                if (cmsg->cmsg_level == SOL_IPV6 &&
+                    cmsg->cmsg_type == IPV6_HOPLIMIT &&
+                    cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+                        int hops = *(int*)CMSG_DATA(cmsg);
+
+                        if (hops != 255) {
+                                log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
+                                return 0;
+                        }
+
+                        break;
+                }
+        }
 
         if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
                 _cleanup_free_ char *addr = NULL;