Date: Thu, 19 Dec 2013 21:49:40 +1100 (EST) From: Bruce Evans <brde@optusnet.com.au> To: Stefan Esser <se@freebsd.org> Cc: svn-src-head@freebsd.org, svn-src-all@freebsd.org, src-committers@freebsd.org Subject: Re: svn commit: r259609 - head/sys/kern Message-ID: <20131219204903.V24189@besplex.bde.org> In-Reply-To: <201312190901.rBJ91ko3036881@svn.freebsd.org> References: <201312190901.rBJ91ko3036881@svn.freebsd.org>
next in thread | previous in thread | raw e-mail | index | archive | help
On Thu, 19 Dec 2013, Stefan Esser wrote: > Log: > Fix overflow for timeout values of more than 68 years, which is the maximum > covered by sbintime (LONG_MAX seconds). Not LONG_MAX seconds, but INT32_MAX seconds. LONG_MAX seconds is about 2**32 times larger on 64-bit arches. sbintimes and their complexity give many more possibilities for overflow. I thought that the new overflow bugs and some old ones were already fixed. > Some programs use timeout values in excess of 1000 years. The conversion > to sbintime caused wrap-around on overflow, which resulted in short or > negative timeout values. This caused long delays on sockets opened by > affected programs (e.g. OpenSSH). > > Kernels compiled without -fno-strict-overflow were not affected, apparently > because the compiler tested the sign of the timeout value before performing > the multiplication that lead to overflow. I think it just gave a garbage value that was accidentally less harmful. > When the -fno-strict-overflow option was added to CFLAGS, this optimization > was disabled and the test was performed on the result of the multiplication. > Negative products were caught and resulted in EINVAL being returned, but > wrap-around to positive values just shortened the timeout value to the > residue of the result that could be represented by sbintime. This shows one reason why -fno-strict-overflow shouldn't be used. It just wastes time to give different undefined behaviour with undocumented details. > The fix is to cap the timeout values at the maximum that can be represented > by sbintime, which is 2^31 - 1 seconds or more than 68 years. 2^31 - 1 is a correct spelling of INT32_MAX, unlike LONG_MAX. > After this change, the kernel can be compiled with -fno-strict-overflow > with no ill effects. > Modified: head/sys/kern/kern_event.c > ============================================================================== > --- head/sys/kern/kern_event.c Thu Dec 19 07:33:07 2013 (r259608) > +++ head/sys/kern/kern_event.c Thu Dec 19 09:01:46 2013 (r259609) > @@ -526,7 +526,8 @@ knote_fork(struct knlist *list, int pid) > static __inline sbintime_t > timer2sbintime(intptr_t data) > { > - > + if (data > LLONG_MAX / SBT_1MS) > + return LLONG_MAX; > return (SBT_1MS * data); > } This has the following style bugs: - it removes the empty line after the (null) declarations - it is missing parentheses around the return value - it uses the long long abomination This has the following type errors: - using the long long abomination is not just a style bug. sbintime_t has type int64_t, not long long. The overflow check will break if long long becomes longer than int64_t - returning LLONG_MAX is usually just a style bug. When long long is longer than int64_t, the return value will overflow, but the implementation-defined behaviour for this is usually to convert it to the correct value. INT64_MAX is a dangerous default maximum timeout. You can't even add the minimum sbintime_t of 2**-32 seconds to this without overflowing. Old timeout code used the following method to reduce the problem of overflowing timeouts: For alarm() and setitimer(), limit the timeout to 100 million seconds (3.17 years) and return EINVAL if it is too large. This works with 32-bit time_t until about 2035. POSIX doesn't allow this. Clamping the time to 100 million seconds isn't allowed either, since the residual time can be seen from applications. Note that 64-bit time_t has little effect on this problem. Uses wishing to overflow the kernel simply ask for a timeout in a timeval of INT64_MAX seconds plus 999999 seconds so that adding just 1 microseconds to it overflow. With seconds in sbintime_t instead of in time_t, overflow occurs much sooner. This is quite broken now: - the limit of 100000000 used to be enforced in itimerfix(), but this was removed in connection with implementing POSIX realtime timers without even breaking the documentaion to match or touching PR(s) about the POSIX non-conformance of the limit. - setitimer() mostly uses sbintimes now, so the old limit is irrelevant and enforcing it in itimerfix() would break the new sbintime code and presumably the not so new realtime timers code. The sbintime code uses a modified limit that is also not allowed by POSIX. This limit is of course undocumented. It is INT32_MAX / 2, so that there is plenty of room for expansion. This is what the above should use, modulo the conformance bug. EINVAL is returned when this limit is exceeded. - select() and poll() were more careful and seem to be correct now. The timeout in poll() is limited to INT_MAX milliseconds, so it fits in sbintime_t. Except this assumes that int is 32 bits. select() limits the timeout to INT32_MAX seconds so that it is representable as an sbintime_t and then does a further overflow check involving INT64_MAX. When overflow would occur, it sets asbt to -1, which I think means an infinite timeout. I don't like this. -1 is a tiny amount less than 0, not near plus infinity. - nanosleep() used to be less careful than select() and poll(), but seems to have been fixed. For some reason, it uses a mixture of the above methods. It limits sleep times to INT32_MAX / 2 seconds but repeats the sleep as necessary so as not to return an error. The logic for this is subtle and I haven't checked it. davide@ has patches related to fixing this, but none seem to have been committed yet, except some old ones that gave some of the above overflow checking. Bruce
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20131219204903.V24189>