NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/39769: race condition in TCP timers



>Number:         39769
>Category:       kern
>Synopsis:       race condition in TCP timers
>Confidential:   no
>Severity:       critical
>Priority:       high
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Oct 19 13:35:00 +0000 2008
>Originator:     Manuel Bouyer
>Release:        NetBSD 3.1.1_PATCH
>Organization:
>Environment:
System: NetBSD ftp.lip6.fr 3.1.1_PATCH NetBSD 3.1.1_PATCH (FTP) #6: Fri Oct 10 
16:47:47 CEST 2008  
bouyer@rock:/dsk/l1/misc/bouyer/tmp/amd64/obj/dsk/l1/misc/bouyer/netbsd-3-1/src/sys/arch/amd64/compile/FTP
 amd64
Architecture: amd64
Machine: amd64
>Description:
        I've got several instances of this panic:
panic: tcp_output REXMT
panic() at netbsd:panic+0x1c8
tcp_segsize() at netbsd:tcp_segsize
tcp_timer_persist() at netbsd:tcp_timer_persist+0x73
softclock() at netbsd:softclock+0x2c9
softintr_dispatch() at netbsd:softintr_dispatch+0x99
DDB lost frame for netbsd:Xsoftclock+0x2d, trying 0xffffffff8069bcd0
Xsoftclock() at netbsd:Xsoftclock+0x2d

        The tcp timers are armed/disarmed, but we don't check if the callback is
        being invoked. One of them at last can cause the panic I'm seeing in
        tcp_output():
                if (win == 0) {
                        TCP_TIMER_DISARM(tp, TCPT_REXMT);
                        tp->t_rxtshift = 0;
                        tp->snd_nxt = tp->snd_una;
                        if (TCP_TIMER_ISARMED(tp, TCPT_PERSIST) == 0)
                                tcp_setpersist(tp);
                }

        If TCPT_REXMT's callback is invoking at this point, it'll be run just
        after tcp_output() exists. tcp_timer_rexmt() will restart the
        TCPT_REXMT timer, so now we have both TCPT_REXMT and TCPT_PERSIST
        pending. When TCPT_PERSIST expires, we get the panic above.

        There are other places where we stop TCPT_REXMT, and potentially
        arm another timer in the same splsoftnet() block (e.g. tcp_input
        stops TCPT_REXMT and may call tcp_output, which may arm TCPT_PERSIST,
        tcp_sack does it too).

        There are also in tcp_output() possible paths where we disarm
        TCPT_PERSIST, and later TCPT_REXMT while TCPT_PERSIST may still be
        invoking. I think there could also cause the tcp_setpersist panic,
        as tcp_timer_persist can call tcp_setpersist().

        As TCP_TIMER_DISARM() will clear the pending and expirted states,
        I think a possible way to close this race is to check callout_expired()
        in the callout handler, and abort the handler if it's not expired.
        See attached patch.

        It looks like the same issue exists in netbsd-4 and current.

>How-To-Repeat:
        Code inspection.
        Run a system with TCP connections and be unlucky.
>Fix:
        I think this patch fixes it. I've been running it on a 3.1.1_PATCH
        server without ill effect. 

Index: tcp_timer.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/tcp_timer.c,v
retrieving revision 1.71
diff -u -r1.71 tcp_timer.c
--- tcp_timer.c 2 Mar 2005 10:20:18 -0000       1.71
+++ tcp_timer.c 10 Oct 2008 16:16:15 -0000
@@ -232,6 +232,10 @@
                splx(s);
                return;
        }
+       if (!callout_expired(&tp->t_delack_ch)) {
+               splx(s);
+               return;
+       }
 
        tp->t_flags |= TF_ACKNOW;
        (void) tcp_output(tp);
@@ -293,6 +297,10 @@
                splx(s);
                return;
        }
+       if (!callout_expired(&tp->t_timer[TCPT_REXMT])) {
+               splx(s);
+               return;
+       }
 
 #ifdef TCP_DEBUG
 #ifdef INET
@@ -453,6 +461,10 @@
                splx(s);
                return;
        }
+       if (!callout_expired(&tp->t_timer[TCPT_PERSIST])) {
+               splx(s);
+               return;
+       }
 
 #ifdef TCP_DEBUG
 #ifdef INET
@@ -520,6 +532,10 @@
                splx(s);
                return;
        }
+       if (!callout_expired(&tp->t_timer[TCPT_KEEP])) {
+               splx(s);
+               return;
+       }
 
 #ifdef TCP_DEBUG
        ostate = tp->t_state;
@@ -607,6 +623,10 @@
                splx(s);
                return;
        }
+       if (!callout_expired(&tp->t_timer[TCPT_2MSL])) {
+               splx(s);
+               return;
+       }
 
        /*
         * 2 MSL timeout went off, clear the SACK scoreboard, reset



Home | Main Index | Thread Index | Old Index