kern/130652: Possible deadlock in rt_check() (sys/net/route.c)
Dmitrij Tejblum
tejblum at yandex-team.ru
Sat Jan 17 08:20:04 PST 2009
>Number: 130652
>Category: kern
>Synopsis: Possible deadlock in rt_check() (sys/net/route.c)
>Confidential: no
>Severity: serious
>Priority: high
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Sat Jan 17 16:20:03 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator: Dmitrij Tejblum
>Release: 7.1-STABLE
>Organization:
OOO Yandex
>Environment:
FreeBSD 7.1-STABLE; net/route.c 1.120.2.7
>Description:
Some excerpt from rt_check():
rt_check()
{
/*1*/ RT_LOCK(rt0);
retry:
...
if ((rt = rt0->rt_gwroute) != NULL) {
/*2*/ RT_LOCK(rt); /* NB: gwroute */
....
}
if (rt == NULL) { /* NOT AN ELSE CLAUSE */
/*3*/ RT_TEMP_UNLOCK(rt0); /* MUST return to undo this */
/*4*/ rt = rtalloc1_fib(rt0->rt_gateway, 1, 0UL, fibnum);
....
/*5*/ RT_RELOCK(rt0);
....
rt0->rt_gwroute = rt;
}
RT_LOCK_ASSERT(rt);
RT_UNLOCK(rt0);
....
}
The function deals with route rt0 and rt. Usually, it locks rt0 in point /*1*/, then locks rt = rt0->rt_gwroute in point /*2*/, then unlock rt0 and done. But sometimes, in lock rt inside rtalloc1_fib() in point /*4*/. Then, in point /*5*/, it locks rt0, which was unlocked in point /*3*/. The order of locking of rt0 and rt is reversed, so a deadlock is possible.
(Also, if after RT_RELOCK(rt0) we found that rt0 is unusable, we should not forget to free rt before retry.)
>How-To-Repeat:
>Fix:
Patch attached with submission follows:
--- net/route.c 2008-12-05 20:40:46.000000000 +0300
+++ net/route.c 2009-01-17 18:59:12.000000000 +0300
@@ -1634,24 +1634,31 @@ retry:
* Relock it and lose the added reference.
* All sorts of things could have happenned while we
* had no lock on it, so check for them.
+ * rt need to be unlocked to avoid possible deadlock.
*/
+ RT_UNLOCK(rt);
RT_RELOCK(rt0);
- if (rt0 == NULL || ((rt0->rt_flags & RTF_UP) == 0))
+ if (rt0 == NULL || ((rt0->rt_flags & RTF_UP) == 0)) {
/* Ru-roh.. what we had is no longer any good */
+ RTFREE(rt);
goto retry;
+ }
/*
* While we were away, someone replaced the gateway.
* Since a reference count is involved we can't just
* overwrite it.
*/
if (rt0->rt_gwroute) {
- if (rt0->rt_gwroute != rt) {
- RTFREE_LOCKED(rt);
- goto retry;
- }
+ if (rt0->rt_gwroute != rt)
+ RTFREE(rt);
} else {
rt0->rt_gwroute = rt;
}
+ /*
+ * Since rt was not locked, we need recheck that
+ * it still may be used (e.g. up)
+ */
+ goto retry;
}
RT_LOCK_ASSERT(rt);
RT_UNLOCK(rt0);
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list