Skip to content

Commit 04d26e7

Browse files
Guillaume Naultdavem330
authored andcommitted
tcp: fix rejected syncookies due to stale timestamps
If no synflood happens for a long enough period of time, then the synflood timestamp isn't refreshed and jiffies can advance so much that time_after32() can't accurately compare them any more. Therefore, we can end up in a situation where time_after32(now, last_overflow + HZ) returns false, just because these two values are too far apart. In that case, the synflood timestamp isn't updated as it should be, which can trick tcp_synq_no_recent_overflow() into rejecting valid syncookies. For example, let's consider the following scenario on a system with HZ=1000: * The synflood timestamp is 0, either because that's the timestamp of the last synflood or, more commonly, because we're working with a freshly created socket. * We receive a new SYN, which triggers synflood protection. Let's say that this happens when jiffies == 2147484649 (that is, 'synflood timestamp' + HZ + 2^31 + 1). * Then tcp_synq_overflow() doesn't update the synflood timestamp, because time_after32(2147484649, 1000) returns false. With: - 2147484649: the value of jiffies, aka. 'now'. - 1000: the value of 'last_overflow' + HZ. * A bit later, we receive the ACK completing the 3WHS. But cookie_v[46]_check() rejects it because tcp_synq_no_recent_overflow() says that we're not under synflood. That's because time_after32(2147484649, 120000) returns false. With: - 2147484649: the value of jiffies, aka. 'now'. - 120000: the value of 'last_overflow' + TCP_SYNCOOKIE_VALID. Of course, in reality jiffies would have increased a bit, but this condition will last for the next 119 seconds, which is far enough to accommodate for jiffie's growth. Fix this by updating the overflow timestamp whenever jiffies isn't within the [last_overflow, last_overflow + HZ] range. That shouldn't have any performance impact since the update still happens at most once per second. Now we're guaranteed to have fresh timestamps while under synflood, so tcp_synq_no_recent_overflow() can safely use it with time_after32() in such situations. Stale timestamps can still make tcp_synq_no_recent_overflow() return the wrong verdict when not under synflood. This will be handled in the next patch. For 64 bits architectures, the problem was introduced with the conversion of ->tw_ts_recent_stamp to 32 bits integer by commit cca9bab ("tcp: use monotonic timestamps for PAWS"). The problem has always been there on 32 bits architectures. Fixes: cca9bab ("tcp: use monotonic timestamps for PAWS") Fixes: 1da177e ("Linux-2.6.12-rc2") Signed-off-by: Guillaume Nault <[email protected]> Signed-off-by: Eric Dumazet <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 537d077 commit 04d26e7

File tree

2 files changed

+16
-2
lines changed

2 files changed

+16
-2
lines changed

include/linux/time.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,17 @@ static inline bool itimerspec64_valid(const struct itimerspec64 *its)
9797
*/
9898
#define time_after32(a, b) ((s32)((u32)(b) - (u32)(a)) < 0)
9999
#define time_before32(b, a) time_after32(a, b)
100+
101+
/**
102+
* time_between32 - check if a 32-bit timestamp is within a given time range
103+
* @t: the time which may be within [l,h]
104+
* @l: the lower bound of the range
105+
* @h: the higher bound of the range
106+
*
107+
* time_before32(t, l, h) returns true if @l <= @t <= @h. All operands are
108+
* treated as 32-bit integers.
109+
*
110+
* Equivalent to !(time_before32(@t, @l) || time_after32(@t, @h)).
111+
*/
112+
#define time_between32(t, l, h) ((u32)(h) - (u32)(l) >= (u32)(t) - (u32)(l))
100113
#endif

include/net/tcp.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,14 +494,15 @@ static inline void tcp_synq_overflow(const struct sock *sk)
494494
reuse = rcu_dereference(sk->sk_reuseport_cb);
495495
if (likely(reuse)) {
496496
last_overflow = READ_ONCE(reuse->synq_overflow_ts);
497-
if (time_after32(now, last_overflow + HZ))
497+
if (!time_between32(now, last_overflow,
498+
last_overflow + HZ))
498499
WRITE_ONCE(reuse->synq_overflow_ts, now);
499500
return;
500501
}
501502
}
502503

503504
last_overflow = tcp_sk(sk)->rx_opt.ts_recent_stamp;
504-
if (time_after32(now, last_overflow + HZ))
505+
if (!time_between32(now, last_overflow, last_overflow + HZ))
505506
tcp_sk(sk)->rx_opt.ts_recent_stamp = now;
506507
}
507508

0 commit comments

Comments
 (0)