rn_walktree*() compute the next leaf before applying a function

to current leaves because function may vanish the current node.

If parent RTA_GENMASK route has a clone (a "cloning clone"), an
rn_walktree_from() starting from parent will cause another walk
starting from clone.  If a function is either rt_fixdelete() or
rt_fixchange(), this recursive walk may vanish the leaf that is
remembered by an outer walk (the "next leaf" above), panicing a
system when it resumes with an outer walk.

The following script paniced my single-user mode booted system:

: sysctl net.inet.ip.forwarding=1
: ipfw add 1 allow ip from any to any
: ifconfig lo0 127.1
: route add -net 10 -genmask 255.255.255.0 127.1
: telnet 10.1			# rt_fixchange() panic
: telnet 10.2
: telnet 10.1
: route delete -net 10		# rt_fixdelete() panic

For the time being, avoid these races by disallowing recursive
walks in rt_fixchange() and rt_fixdelete().

Also, make a slight optimization in the rtrequest(RTM_RESOLVE)
case: there is no reason to call rt_fixchange() in this case.

PR:		kern/37606
MFC after:	5 days
This commit is contained in:
Ruslan Ermilov 2002-12-23 13:12:41 +00:00
parent 48096bb398
commit 36fea5de60

View File

@ -741,7 +741,8 @@ rtrequest1(req, info, ret_nrt)
* it doesn't fire when we call it there because the node
* hasn't been added to the tree yet.
*/
if (!(rt->rt_flags & RTF_HOST) && rt_mask(rt) != 0) {
if (req == RTM_ADD &&
!(rt->rt_flags & RTF_HOST) && rt_mask(rt) != 0) {
struct rtfc_arg arg;
arg.rnh = rnh;
arg.rt0 = rt;
@ -787,7 +788,8 @@ rt_fixdelete(rn, vp)
struct rtentry *rt = (struct rtentry *)rn;
struct rtentry *rt0 = vp;
if (rt->rt_parent == rt0 && !(rt->rt_flags & RTF_PINNED)) {
if (rt->rt_parent == rt0 &&
!(rt->rt_flags & (RTF_PINNED | RTF_CLONING | RTF_PRCLONING))) {
return rtrequest(RTM_DELETE, rt_key(rt),
(struct sockaddr *)0, rt_mask(rt),
rt->rt_flags, (struct rtentry **)0);
@ -829,9 +831,10 @@ rt_fixchange(rn, vp)
printf("rt_fixchange: rt %p, rt0 %p\n", rt, rt0);
#endif
if (!rt->rt_parent || (rt->rt_flags & RTF_PINNED)) {
if (!rt->rt_parent ||
(rt->rt_flags & (RTF_PINNED | RTF_CLONING | RTF_PRCLONING))) {
#ifdef DEBUG
if(rtfcdebug) printf("no parent or pinned\n");
if(rtfcdebug) printf("no parent, pinned or cloning\n");
#endif
return 0;
}