Index: sys/net/rtsock.c =================================================================== --- sys/net/rtsock.c +++ sys/net/rtsock.c @@ -126,6 +126,14 @@ #endif /* COMPAT_FREEBSD32 */ +struct linear_buffer { + char *base; /* Base allocated memory pointer */ + uint32_t offset; /* Currently used offset */ + uint32_t size; /* Total buffer size */ +}; +#define SCRATCH_BUFFER_SIZE 1024 + + MALLOC_DEFINE(M_RTABLE, "routetbl", "routing tables"); /* NB: these are not modified */ @@ -174,7 +182,7 @@ struct walkarg *w, int *plen); static int rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo); -static int cleanup_xaddrs(struct rt_addrinfo *info); +static int cleanup_xaddrs(struct rt_addrinfo *info, struct linear_buffer *lb); static int sysctl_dumpentry(struct rtentry *rt, void *vw); static int sysctl_dumpnhop(struct rtentry *rt, struct nhop_object *nh, uint32_t weight, struct walkarg *w); @@ -618,7 +626,8 @@ * Returns 0 on success. */ static int -fill_addrinfo(struct rt_msghdr *rtm, int len, u_int fibnum, struct rt_addrinfo *info) +fill_addrinfo(struct rt_msghdr *rtm, int len, struct linear_buffer *lb, u_int fibnum, + struct rt_addrinfo *info) { int error; sa_family_t saf; @@ -638,7 +647,7 @@ return (EINVAL); info->rti_flags = rtm->rtm_flags; - error = cleanup_xaddrs(info); + error = cleanup_xaddrs(info, lb); if (error != 0) return (error); saf = info->rti_info[RTAX_DST]->sa_family; @@ -875,6 +884,45 @@ #endif } +static int +update_rtm_from_info(struct rt_addrinfo *info, struct rt_msghdr **prtm, + int alloc_len) +{ + struct rt_msghdr *rtm, *orig_rtm = NULL; + struct walkarg w; + int len; + + rtm = *prtm; + /* Check if we need to realloc storage */ + rtsock_msg_buffer(rtm->rtm_type, info, NULL, &len); + if (len > alloc_len) { + struct rt_msghdr *tmp_rtm; + + tmp_rtm = malloc(len, M_TEMP, M_NOWAIT); + if (tmp_rtm == NULL) + return (ENOBUFS); + bcopy(rtm, tmp_rtm, rtm->rtm_msglen); + orig_rtm = rtm; + rtm = tmp_rtm; + alloc_len = len; + + /* + * Delay freeing original rtm as info contains + * data referencing it. + */ + } + + w.w_tmem = (caddr_t)rtm; + w.w_tmemsize = alloc_len; + rtsock_msg_buffer(rtm->rtm_type, info, &w, &len); + rtm->rtm_addrs = info->rti_addrs; + + if (orig_rtm != NULL) + free(orig_rtm, M_TEMP); + *prtm = rtm; + return (0); +} + /* * Update sockaddrs, flags, etc in @prtm based on @rc data. @@ -888,11 +936,10 @@ update_rtm_from_rc(struct rt_addrinfo *info, struct rt_msghdr **prtm, int alloc_len, struct rib_cmd_info *rc, struct nhop_object *nh) { - struct walkarg w; union sockaddr_union saun; - struct rt_msghdr *rtm, *orig_rtm = NULL; + struct rt_msghdr *rtm; struct ifnet *ifp; - int error, len; + int error; rtm = *prtm; union sockaddr_union sa_dst, sa_mask; @@ -924,28 +971,8 @@ } else if (ifp != NULL) rtm->rtm_index = ifp->if_index; - /* Check if we need to realloc storage */ - rtsock_msg_buffer(rtm->rtm_type, info, NULL, &len); - if (len > alloc_len) { - struct rt_msghdr *tmp_rtm; - - tmp_rtm = malloc(len, M_TEMP, M_NOWAIT); - if (tmp_rtm == NULL) - return (ENOBUFS); - bcopy(rtm, tmp_rtm, rtm->rtm_msglen); - orig_rtm = rtm; - rtm = tmp_rtm; - alloc_len = len; - - /* - * Delay freeing original rtm as info contains - * data referencing it. - */ - } - - w.w_tmem = (caddr_t)rtm; - w.w_tmemsize = alloc_len; - rtsock_msg_buffer(rtm->rtm_type, info, &w, &len); + if ((error = update_rtm_from_info(info, prtm, alloc_len)) != 0) + return (error); rtm->rtm_flags = rc->rc_rt->rte_flags | nhop_get_rtflags(nh); if (rtm->rtm_flags & RTF_GWFLAG_COMPAT) @@ -953,11 +980,6 @@ (rtm->rtm_flags & ~RTF_GWFLAG_COMPAT); rt_getmetrics(rc->rc_rt, nh, &rtm->rtm_rmx); rtm->rtm_rmx.rmx_weight = rc->rc_nh_weight; - rtm->rtm_addrs = info->rti_addrs; - - if (orig_rtm != NULL) - free(orig_rtm, M_TEMP); - *prtm = rtm; return (0); } @@ -982,6 +1004,17 @@ } #endif +static struct sockaddr * +alloc_sockaddr_aligned(struct linear_buffer *lb, int len) +{ + len |= (sizeof(uint64_t) - 1); + if (lb->offset + len > lb->size) + return (NULL); + struct sockaddr *sa = (struct sockaddr *)(lb->base + lb->offset); + lb->offset += len; + return (sa); +} + /*ARGSUSED*/ static int route_output(struct mbuf *m, struct socket *so, ...) @@ -1019,12 +1052,17 @@ * buffer aligned on 1k boundaty. */ alloc_len = roundup2(len, 1024); - if ((rtm = malloc(alloc_len, M_TEMP, M_NOWAIT)) == NULL) + int total_len = alloc_len + SCRATCH_BUFFER_SIZE; + if ((rtm = malloc(total_len, M_TEMP, M_NOWAIT)) == NULL) senderr(ENOBUFS); m_copydata(m, 0, len, (caddr_t)rtm); bzero(&info, sizeof(info)); nh = NULL; + struct linear_buffer lb = { + .base = (char *)rtm + alloc_len, + .size = SCRATCH_BUFFER_SIZE, + }; if (rtm->rtm_version != RTM_VERSION) { /* Do not touch message since format is unknown */ @@ -1039,19 +1077,19 @@ * caller PID and error value. */ - if ((error = fill_addrinfo(rtm, len, fibnum, &info)) != 0) { + if ((error = fill_addrinfo(rtm, len, &lb, fibnum, &info)) != 0) { senderr(error); } + /* fill_addringo() embeds scope into IPv6 addresses */ +#ifdef INET6 + rti_need_deembed = 1; +#endif saf = info.rti_info[RTAX_DST]->sa_family; /* support for new ARP code */ if (rtm->rtm_flags & RTF_LLDATA) { error = lla_rt_output(rtm, &info); -#ifdef INET6 - if (error == 0) - rti_need_deembed = 1; -#endif goto flush; } @@ -1064,7 +1102,6 @@ error = EINVAL; if (error != 0) senderr(error); - /* TODO: rebuild rtm from scratch */ } switch (rtm->rtm_type) { @@ -1076,9 +1113,6 @@ } error = rib_action(fibnum, rtm->rtm_type, &info, &rc); if (error == 0) { -#ifdef INET6 - rti_need_deembed = 1; -#endif #ifdef ROUTE_MPATH if (NH_IS_NHGRP(rc.rc_nh_new) || (rc.rc_nh_old && NH_IS_NHGRP(rc.rc_nh_old))) { @@ -1107,12 +1141,7 @@ } #endif nh = rc.rc_nh_old; - goto report; } -#ifdef INET6 - /* rt_msg2() will not be used when RTM_DELETE fails. */ - rti_need_deembed = 1; -#endif break; case RTM_GET: @@ -1121,13 +1150,18 @@ senderr(error); nh = rc.rc_nh_new; -report: if (!can_export_rte(curthread->td_ucred, info.rti_info[RTAX_NETMASK] == NULL, info.rti_info[RTAX_DST])) { senderr(ESRCH); } + break; + default: + senderr(EOPNOTSUPP); + } + + if (error == 0) { error = update_rtm_from_rc(&info, &rtm, alloc_len, &rc, nh); /* * Note that some sockaddr pointers may have changed to @@ -1144,12 +1178,6 @@ #ifdef INET6 rti_need_deembed = 0; #endif - if (error != 0) - senderr(error); - break; - - default: - senderr(EOPNOTSUPP); } flush: @@ -1171,6 +1199,10 @@ bcopy(sin6, info.rti_info[i], sizeof(*sin6)); } + if (update_rtm_from_info(&info, &rtm, alloc_len) != 0) { + if (error != 0) + error = ENOBUFS; + } } } #endif @@ -1347,9 +1379,10 @@ } static int -cleanup_xaddrs_gateway(struct rt_addrinfo *info) +cleanup_xaddrs_gateway(struct rt_addrinfo *info, struct linear_buffer *lb) { struct sockaddr *gw = info->rti_info[RTAX_GATEWAY]; + struct sockaddr *sa; if (info->rti_flags & RTF_LLDATA) return (cleanup_xaddrs_lladdr(info)); @@ -1359,11 +1392,17 @@ case AF_INET: { struct sockaddr_in *gw_sin = (struct sockaddr_in *)gw; - if (gw_sin->sin_len < sizeof(struct sockaddr_in)) { + + /* Ensure reads do not go beyoud SA boundary */ + if (SA_SIZE(gw) < offsetof(struct sockaddr_in, sin_zero)) { printf("gw sin_len too small\n"); return (EINVAL); } - fill_sockaddr_inet(gw_sin, gw_sin->sin_addr); + sa = alloc_sockaddr_aligned(lb, sizeof(struct sockaddr_in)); + if (sa == NULL) + return (ENOBUFS); + fill_sockaddr_inet((struct sockaddr_in *)sa, gw_sin->sin_addr); + info->rti_info[RTAX_GATEWAY] = sa; } break; #endif @@ -1389,13 +1428,17 @@ printf("gw sdl_len too small\n"); return (EINVAL); } + sa = alloc_sockaddr_aligned(lb, sizeof(struct sockaddr_dl_short)); + if (sa == NULL) + return (ENOBUFS); const struct sockaddr_dl_short sdl = { .sdl_family = AF_LINK, - .sdl_len = sdl_min_len, + .sdl_len = sizeof(struct sockaddr_dl_short), .sdl_index = gw_sdl->sdl_index, }; - memcpy(gw_sdl, &sdl, sdl_min_len); + *((struct sockaddr_dl_short *)sa) = sdl; + info->rti_info[RTAX_GATEWAY] = sa; break; } } @@ -1413,14 +1456,21 @@ #ifdef INET static int -cleanup_xaddrs_inet(struct rt_addrinfo *info) +cleanup_xaddrs_inet(struct rt_addrinfo *info, struct linear_buffer *lb) { struct sockaddr_in *dst_sa, *mask_sa; + const int sa_len = sizeof(struct sockaddr_in); /* Check & fixup dst/netmask combination first */ dst_sa = (struct sockaddr_in *)info->rti_info[RTAX_DST]; mask_sa = (struct sockaddr_in *)info->rti_info[RTAX_NETMASK]; + /* Ensure reads do not go beyound the buffer size */ + if (SA_SIZE(dst_sa) < offsetof(struct sockaddr_in, sin_zero)) + return (EINVAL); + if ((mask_sa != NULL) && (SA_SIZE(mask_sa) < offsetof(struct sockaddr_in, sin_zero))) + return (EINVAL); + struct in_addr mask = { .s_addr = mask_sa ? mask_sa->sin_addr.s_addr : INADDR_BROADCAST, }; @@ -1428,24 +1478,23 @@ .s_addr = htonl(ntohl(dst_sa->sin_addr.s_addr) & ntohl(mask.s_addr)) }; - if (dst_sa->sin_len < sizeof(struct sockaddr_in)) { - printf("dst sin_len too small\n"); - return (EINVAL); - } - if (mask_sa && mask_sa->sin_len < sizeof(struct sockaddr_in)) { - printf("mask sin_len too small\n"); - return (EINVAL); - } + /* Construct new "clean" dst/mask sockaddresses */ + if ((dst_sa = (struct sockaddr_in *)alloc_sockaddr_aligned(lb, sa_len)) == NULL) + return (ENOBUFS); fill_sockaddr_inet(dst_sa, dst); + info->rti_info[RTAX_DST] = (struct sockaddr *)dst_sa; - if (mask.s_addr != INADDR_BROADCAST) + if (mask.s_addr != INADDR_BROADCAST) { + if ((mask_sa = (struct sockaddr_in *)alloc_sockaddr_aligned(lb, sa_len)) == NULL) + return (ENOBUFS); fill_sockaddr_inet(mask_sa, mask); - else + info->rti_info[RTAX_NETMASK] = (struct sockaddr *)mask_sa; + } else remove_netmask(info); /* Check gateway */ if (info->rti_info[RTAX_GATEWAY] != NULL) - return (cleanup_xaddrs_gateway(info)); + return (cleanup_xaddrs_gateway(info, lb)); return (0); } @@ -1453,18 +1502,17 @@ #ifdef INET6 static int -cleanup_xaddrs_inet6(struct rt_addrinfo *info) +cleanup_xaddrs_inet6(struct rt_addrinfo *info, struct linear_buffer *lb) { + struct sockaddr *sa; struct sockaddr_in6 *dst_sa, *mask_sa; - struct in6_addr mask; + struct in6_addr mask, *dst; + const int sa_len = sizeof(struct sockaddr_in6); /* Check & fixup dst/netmask combination first */ dst_sa = (struct sockaddr_in6 *)info->rti_info[RTAX_DST]; mask_sa = (struct sockaddr_in6 *)info->rti_info[RTAX_NETMASK]; - mask = mask_sa ? mask_sa->sin6_addr : in6mask128; - IN6_MASK_ADDR(&dst_sa->sin6_addr, &mask); - if (dst_sa->sin6_len < sizeof(struct sockaddr_in6)) { printf("dst sin6_len too small\n"); return (EINVAL); @@ -1473,23 +1521,34 @@ printf("mask sin6_len too small\n"); return (EINVAL); } - fill_sockaddr_inet6(dst_sa, &dst_sa->sin6_addr, 0); - if (!IN6_ARE_ADDR_EQUAL(&mask, &in6mask128)) - fill_sockaddr_inet6(mask_sa, &mask, 0); - else + mask = mask_sa ? mask_sa->sin6_addr : in6mask128; + dst = &dst_sa->sin6_addr; + IN6_MASK_ADDR(dst, &mask); + + if ((sa = alloc_sockaddr_aligned(lb, sa_len)) == NULL) + return (ENOBUFS); + fill_sockaddr_inet6((struct sockaddr_in6 *)sa, dst, 0); + info->rti_info[RTAX_DST] = sa; + + if (!IN6_ARE_ADDR_EQUAL(&mask, &in6mask128)) { + if ((sa = alloc_sockaddr_aligned(lb, sa_len)) == NULL) + return (ENOBUFS); + fill_sockaddr_inet6((struct sockaddr_in6 *)sa, &mask, 0); + info->rti_info[RTAX_NETMASK] = sa; + } else remove_netmask(info); /* Check gateway */ if (info->rti_info[RTAX_GATEWAY] != NULL) - return (cleanup_xaddrs_gateway(info)); + return (cleanup_xaddrs_gateway(info, lb)); return (0); } #endif static int -cleanup_xaddrs(struct rt_addrinfo *info) +cleanup_xaddrs(struct rt_addrinfo *info, struct linear_buffer *lb) { int error = EAFNOSUPPORT; @@ -1508,12 +1567,12 @@ switch (info->rti_info[RTAX_DST]->sa_family) { #ifdef INET case AF_INET: - error = cleanup_xaddrs_inet(info); + error = cleanup_xaddrs_inet(info, lb); break; #endif #ifdef INET6 case AF_INET6: - error = cleanup_xaddrs_inet6(info); + error = cleanup_xaddrs_inet6(info, lb); break; #endif } @@ -2092,9 +2151,9 @@ } /* - * Checks if rte can be exported v.r.t jails/vnets. + * Checks if rte can be exported w.r.t jails/vnets. * - * Returns 1 if it can, 0 otherwise. + * Returns true if it can, false otherwise. */ static bool can_export_rte(struct ucred *td_ucred, bool rt_is_host, Index: tests/sys/net/routing/rtsock_common.h =================================================================== --- tests/sys/net/routing/rtsock_common.h +++ tests/sys/net/routing/rtsock_common.h @@ -454,7 +454,7 @@ b6 = (const struct sockaddr_in6 *)b; if (!IN6_ARE_ADDR_EQUAL(&a6->sin6_addr, &b6->sin6_addr)) { inet_ntop(AF_INET6, &a6->sin6_addr, a_s, sizeof(a_s)); - inet_ntop(AF_INET6, &b6->sin6_addr, a_s, sizeof(a_s)); + inet_ntop(AF_INET6, &b6->sin6_addr, b_s, sizeof(b_s)); snprintf(msg, sz, "addr diff: %s vs %s", a_s, b_s); return 0; }