Date: Sat, 16 Jun 2018 15:39:24 +1000 (EST) From: Bruce Evans <brde@optusnet.com.au> To: Gleb Smirnoff <glebius@freebsd.org> Cc: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: Re: svn commit: r335242 - head/sys/kern Message-ID: <20180616123220.K976@besplex.bde.org> In-Reply-To: <201806152136.w5FLaGCP074334@repo.freebsd.org> References: <201806152136.w5FLaGCP074334@repo.freebsd.org>
next in thread | previous in thread | raw e-mail | index | archive | help
On Fri, 15 Jun 2018, Gleb Smirnoff wrote: > Log: > Since 'ticks' is an int, it may wrap around and cr_ticks at a certain > counter_rate will be greater than ticks, resulting in counter_ratecheck() > failure. To fix this take an absolute value of the difference between > ticks and cr_ticks. This only works accidentally, and only half as well as possible. It is much easier to see that the normal method to u_int works as well as possible, esepecially for cases where it doesn't work. > Modified: head/sys/kern/subr_counter.c > ============================================================================== > --- head/sys/kern/subr_counter.c Fri Jun 15 21:23:03 2018 (r335241) > +++ head/sys/kern/subr_counter.c Fri Jun 15 21:36:16 2018 (r335242) > @@ -140,7 +140,7 @@ counter_ratecheck(struct counter_rate *cr, int64_t lim > val = cr->cr_over; > now = ticks; > > - if (now - cr->cr_ticks >= hz) { > + if (abs(now - cr->cr_ticks) >= hz) { > /* > * Time to clear the structure, we are in the next second. > * First try unlocked read, and then proceed with atomic. The normal way writing this is '(u_int)(now - cr->cr_ticks) >= hz'. Networking code has hundreds of examples of this just in the SEQ_* macros. The SEQ_*() macros handle signed differences of unsigned sequence numbers. Sequence numbers that need to be compared must be kept closer together than their full unsigned range for this to work. Here we want unsigned difference of (bogusly) signed tick counts, and the full unsigned range is available, but was not used (giving large bugs). The tick counts are usually kept close together by resetting cr_ticks often, but when this function is not called for a long time (~2**31 ticks) cr_ticks has not been reset, and then the check for resetting it remains broken for another long time (~2**31 ticks). Overflow of 'ticks' itself eventually accidentally fixes the brokenness of the check. The cycle repeats every 2**32 ticks. The only correct (not so quick) ways to fix this are to enlarge tick counters so that overflow never occurs, or to arrange to call this function more often (for every counter). Otherwise, undetectable wrap occurs whenever the function is not called for 2**32 ticks. Then further calls are incorrectly rate-limited for the next second. With hz = 1000, this only happens every 49.7 days, so isn't much of a problem. This commit fixes the larger problem that detectable but undetected overflow occurs whenever the function is not called for 2**31 ticks. Then further calls are incorrectly rate-limited for the next 2**31 ticks, With hz = 1000, this gives 24.8 days of brokeness which is too much. But using abs() only works accidentally. It gives 2 more intervals of brokenness every 2**32 ticks. These intervals happen to be short, so aren't much of a problem, but understanding this is harder: - when the unsigned difference (mod 2**32) is INT32_MAX + 1U, the signed difference is INT32_MIN. abs() on this overflows to INT32_MIN. The check misclassifies this and gives another tick of rate limiting every cycle. 1 tick of brokenness is not very long. - when the unsigned difference (mod 2**32) is >= UINT32_MAX - hz and <= UINT32_MAX, the signed difference is >= 1 - hz and <= -1. abs() on this gives a value between 1 and hz - 1. The check misclassifies this and gives another tick of rate limiting every cycle. abs() essentially gives a complement of the correct value, and the range here is almost a reflection of the range from 2**32 to 2**32 + hz - 1 starting 1 tick higher. 1 second (less 1 tick) of brokenness is a bit too long, but we already have to put up with 1 second of brokenness after undetectable wrap. - when the unsigned difference (mod 2**32) is >= INT32_MAX + 2U and < UINT32_MAX - hz, the value given by abs() happens to be >= hz, so the check accidentally does the correct classification. This fixes most broken cases. Bruce
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20180616123220.K976>