On Mon, Dec 07, 2015 at 04:36:17PM +0100, Martin Pieuchot wrote: > The rtrequest_delete() refactoring exposed an existing bug and > introduced a regression, both triggered by the same KASSERT(). > > The regression has been reported there: > https://marc.info/?l=openbsd-bugs&m=144943901304713&w=2 > > The problem is that rt_if_remove() will triggers a rtflushclone1() if a > RTF_CLONING route is attached to an interface. In this case > rtdeletemsg() tries to get the interface pointer from rt_ifidx and the > KASSERT() fires. > > The bug exposed by the same KASSERT() can be triggered by a call to > rt_ifa_del() when dhclient(8) tries to add an address already configured > on the system. In this case the kernel calls rtrequest_delete() with > a wrong ifp. > > Diff below fixes these two bugs. It's big because I don't want to put > two customs checks in a generic function. This diff is built upon my > previous rtdeletemsg() one. > > It introduces rt_delete() which deletes a route *without* doing a > lookup. There's various good reasons for that. > - First of all I don't want to take the risk of matching a similar > multipath route. The rt_ifa_del() bug is an example. > - Secondly I'd like to avoid a recursive rtable_* call when routes > are deleted from rtable_walk(). > - Then I'd rather keep userland-specific checks inside rtrequest() > which will ends up in rtsock.c one day. > - Finally it cleans one use of "struct rt_addrinfo" which should be > limited to userland in order to get rid of RTA_NETMASK in kernel. > > Note that it introduces a behavior change. If rtdeletemsg() fails to > delete a route no message is reported to userland. I believe this was > only used for debugging.
OK bluhm@ I have merged the diff to -current before reviewing. There it looks like this. Index: net/route.c =================================================================== RCS file: /data/mirror/openbsd/cvs/src/sys/net/route.c,v retrieving revision 1.292 diff -u -p -r1.292 route.c --- net/route.c 11 Dec 2015 08:58:23 -0000 1.292 +++ net/route.c 16 Dec 2015 18:40:16 -0000 @@ -159,8 +159,7 @@ struct sockaddr *rt_plentosa(sa_family_t struct ifaddr *ifa_ifwithroute(int, struct sockaddr *, struct sockaddr *, u_int); -int rtrequest_delete(struct rt_addrinfo *, u_int8_t, struct ifnet *, - struct rtentry **, u_int); +int rt_delete(struct rtentry *, struct ifnet *, unsigned int); #ifdef DDB void db_print_sa(struct sockaddr *); @@ -613,34 +612,20 @@ out: } /* - * Delete a route and generate a message + * Delete a route and generate a message. The caller must hold a reference + * on ``rt''. */ int -rtdeletemsg(struct rtentry *rt, struct ifnet *ifp, u_int tableid) +rtdeletemsg(struct rtentry *rt, struct ifnet *ifp, unsigned int rtableid) { int error; - struct rt_addrinfo info; - unsigned int ifidx; - struct sockaddr_in6 sa_mask; KASSERT(rt->rt_ifidx == ifp->if_index); - /* - * Request the new route so that the entry is not actually - * deleted. That will allow the information being reported to - * be accurate (and consistent with route_output()). - */ - bzero((caddr_t)&info, sizeof(info)); - info.rti_info[RTAX_DST] = rt_key(rt); - if (!ISSET(rt->rt_flags, RTF_HOST)) - info.rti_info[RTAX_NETMASK] = rt_plen2mask(rt, &sa_mask); - info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; - info.rti_flags = rt->rt_flags; - ifidx = rt->rt_ifidx; - error = rtrequest_delete(&info, rt->rt_priority, ifp, &rt, tableid); - rt_missmsg(RTM_DELETE, &info, info.rti_flags, ifidx, error, tableid); + error = rt_delete(rt, ifp, rtableid); if (error == 0) - rtfree(rt); + rt_sendmsg(rt, RTM_DELETE, rtableid); + return (error); } @@ -671,10 +656,13 @@ rtflushclone1(struct rtentry *rt, void * * the routes are being purged by rt_if_remove(). */ if (ifp == NULL) - return 0; + return 0; - if (ISSET(rt->rt_flags, RTF_CLONED) && rtequal(rt->rt_parent, parent)) - rtdeletemsg(rt, ifp, id); + if (ISSET(rt->rt_flags, RTF_CLONED) && rtequal(rt->rt_parent, parent)) { + rtref(rt); + rtdeletemsg(rt, ifp, id); + rtfree(rt); + } if_put(ifp); return 0; @@ -818,60 +806,20 @@ rt_getifa(struct rt_addrinfo *info, u_in } int -rtrequest_delete(struct rt_addrinfo *info, u_int8_t prio, struct ifnet *ifp, - struct rtentry **ret_nrt, u_int tableid) +rt_delete(struct rtentry *rt, struct ifnet *ifp, unsigned int rtableid) { - struct rtentry *rt; - int error; - - splsoftassert(IPL_SOFTNET); + struct sockaddr_in6 sa_mask; - if (!rtable_exists(tableid)) - return (EAFNOSUPPORT); - rt = rtable_lookup(tableid, info->rti_info[RTAX_DST], - info->rti_info[RTAX_NETMASK], info->rti_info[RTAX_GATEWAY], prio); - if (rt == NULL) - return (ESRCH); + KASSERT(ifp->if_index == rt->rt_ifidx); - /* Make sure that's the route the caller want to delete. */ - if (ifp != NULL && ifp->if_index != rt->rt_ifidx) { - rtfree(rt); - return (ESRCH); - } - -#ifndef SMALL_KERNEL - /* - * If we got multipath routes, we require users to specify - * a matching gateway. - */ - if ((rt->rt_flags & RTF_MPATH) && - info->rti_info[RTAX_GATEWAY] == NULL) { - rtfree(rt); - return (ESRCH); - } -#endif - - /* - * Since RTP_LOCAL cannot be set by userland, make - * sure that local routes are only modified by the - * kernel. - */ - if ((rt->rt_flags & (RTF_LOCAL|RTF_BROADCAST)) && - prio != RTP_LOCAL) { - rtfree(rt); - return (EINVAL); - } + splsoftassert(IPL_SOFTNET); - error = rtable_delete(tableid, info->rti_info[RTAX_DST], - info->rti_info[RTAX_NETMASK], rt); - if (error != 0) { - rtfree(rt); + if (rtable_delete(rtableid, rt_key(rt), rt_plen2mask(rt, &sa_mask), rt)) return (ESRCH); - } /* clean up any cloned children */ if ((rt->rt_flags & RTF_CLONING) != 0) - rtflushclone(tableid, rt); + rtflushclone(rtableid, rt); rtfree(rt->rt_gwroute); rt->rt_gwroute = NULL; @@ -881,23 +829,10 @@ rtrequest_delete(struct rt_addrinfo *inf rt->rt_flags &= ~RTF_UP; - if (ifp == NULL) { - ifp = if_get(rt->rt_ifidx); - KASSERT(ifp != NULL); - ifp->if_rtrequest(ifp, RTM_DELETE, rt); - if_put(ifp); - } else { - KASSERT(ifp->if_index == rt->rt_ifidx); - ifp->if_rtrequest(ifp, RTM_DELETE, rt); - } + ifp->if_rtrequest(ifp, RTM_DELETE, rt); atomic_inc_int(&rttrash); - if (ret_nrt != NULL) - *ret_nrt = rt; - else - rtfree(rt); - return (0); } @@ -924,9 +859,50 @@ rtrequest(int req, struct rt_addrinfo *i info->rti_info[RTAX_NETMASK] = NULL; switch (req) { case RTM_DELETE: - error = rtrequest_delete(info, prio, NULL, ret_nrt, tableid); - if (error) + rt = rtable_lookup(tableid, info->rti_info[RTAX_DST], + info->rti_info[RTAX_NETMASK], + info->rti_info[RTAX_GATEWAY], prio); + if (rt == NULL) + return (ESRCH); + +#ifndef SMALL_KERNEL + /* + * If we got multipath routes, we require users to specify + * a matching gateway. + */ + if (ISSET(rt->rt_flags, RTF_MPATH) && + info->rti_info[RTAX_GATEWAY] == NULL) { + rtfree(rt); + return (ESRCH); + } +#endif + + /* + * Since RTP_LOCAL cannot be set by userland, make + * sure that local routes are only modified by the + * kernel. + */ + if (ISSET(rt->rt_flags, RTF_LOCAL|RTF_BROADCAST) && + prio != RTP_LOCAL) { + rtfree(rt); + return (EINVAL); + } + + ifp = if_get(rt->rt_ifidx); + KASSERT(ifp != NULL); + error = rt_delete(rt, ifp, tableid); + if_put(ifp); + + if (error) { + rtfree(rt); return (error); + } + + if (ret_nrt != NULL) + *ret_nrt = rt; + else + rtfree(rt); + break; case RTM_RESOLVE: @@ -1265,8 +1241,8 @@ rt_ifa_del(struct ifaddr *ifa, int flags struct rtentry *rt; struct mbuf *m = NULL; struct sockaddr *deldst; - struct rt_addrinfo info; - struct sockaddr_rtlabel sa_rl; + struct sockaddr *mask = NULL; + struct sockaddr *gateway = NULL; unsigned int rtableid = ifp->if_rdomain; uint8_t prio = ifp->if_priority + RTP_STATIC; int error; @@ -1286,16 +1262,10 @@ rt_ifa_del(struct ifaddr *ifa, int flags dst = deldst; } - memset(&info, 0, sizeof(info)); - info.rti_ifa = ifa; - info.rti_flags = flags; - info.rti_info[RTAX_DST] = dst; if ((flags & RTF_LLINFO) == 0) - info.rti_info[RTAX_GATEWAY] = ifa->ifa_addr; - info.rti_info[RTAX_LABEL] = rtlabel_id2sa(ifp->if_rtlabelid, &sa_rl); - + gateway = ifa->ifa_addr; if ((flags & RTF_HOST) == 0) - info.rti_info[RTAX_NETMASK] = ifa->ifa_netmask; + mask = ifa->ifa_netmask; if (flags & (RTF_LOCAL|RTF_BROADCAST)) prio = RTP_LOCAL; @@ -1303,13 +1273,32 @@ rt_ifa_del(struct ifaddr *ifa, int flags if (flags & RTF_CONNECTED) prio = RTP_CONNECTED; - error = rtrequest_delete(&info, prio, ifp, &rt, rtableid); - if (error == 0) { - rt_sendmsg(rt, RTM_DELETE, rtableid); - if (flags & RTF_LOCAL) - rt_sendaddrmsg(rt, RTM_DELADDR); + rt = rtable_lookup(rtableid, dst, mask, gateway, prio); + if (rt == NULL) + return (ESRCH); + +#ifndef SMALL_KERNEL + /* + * If we got multipath routes, we require users to specify + * a matching gateway. + */ + if (ISSET(rt->rt_flags, RTF_MPATH) && gateway == NULL) { rtfree(rt); + return (ESRCH); } +#endif + + /* Make sure that's the route the caller want to delete. */ + if (rt->rt_ifidx != ifp->if_index) { + rtfree(rt); + return (ESRCH); + } + + error = rtdeletemsg(rt, ifp, rtableid); + if (error == 0 && (flags & RTF_LOCAL)) + rt_sendaddrmsg(rt, RTM_DELADDR); + rtfree(rt); + if (m != NULL) m_free(m); @@ -1721,9 +1710,12 @@ rt_if_remove_rtdelete(struct rtentry *rt struct ifnet *ifp = vifp; if (rt->rt_ifidx == ifp->if_index) { - int cloning = (rt->rt_flags & RTF_CLONING); + int error, cloning = ISSET(rt->rt_flags, RTF_CLONING); - if (rtdeletemsg(rt, ifp, id) == 0 && cloning) + rtref(rt); + error = rtdeletemsg(rt, ifp, id); + rtfree(rt); + if (error == 0 && cloning) return (EAGAIN); } @@ -1781,7 +1773,9 @@ rt_if_linkstate_change(struct rtentry *r * clone a new route from a better source. */ if (rt->rt_flags & RTF_CLONED) { + rtref(rt); rtdeletemsg(rt, ifp, id); + rtfree(rt); return (0); } /* take route down */