diff options
author | Tom Gundersen <teg@jklm.no> | 2015-11-23 15:59:58 +0100 |
---|---|---|
committer | Tom Gundersen <teg@jklm.no> | 2015-11-25 18:30:31 +0100 |
commit | cddf4d81eacfc81cf761619fcb67bc07a744a6d1 (patch) | |
tree | 5ad00fafa1d883eee418fd5cd048cc8c5b49d0c8 /src/libsystemd-network/sd-ndisc.c | |
parent | 6d06ac1faa1b06a9fb5793c970bccd5b47825d07 (diff) |
sd-ndisc: better validate RA packets
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>.
Diffstat (limited to 'src/libsystemd-network/sd-ndisc.c')
-rw-r--r-- | src/libsystemd-network/sd-ndisc.c | 58 |
1 files changed, 47 insertions, 11 deletions
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 249586f048..f2bce3b99f 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -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; |