svn commit: r312702 - in head/sys: kern libkern sys

Bruce Evans brde at optusnet.com.au
Tue Jan 31 12:33:47 UTC 2017


On Wed, 25 Jan 2017, Bruce Evans wrote:

> On Tue, 24 Jan 2017, Conrad E. Meyer wrote:
>
>> Log:
>>  Use time_t for intermediate values to avoid overflow in clock_ts_to_ct
>
> This is bogus.  time_t is for storing times in seconds, not for times in
> days, hours or minutes.

These bugs are still present.

Bounds checking for presposterous values in privileged syscalls is
silly, especially it is incomplete.  There are thousands of sysctls
that privileged users can use to shoot themself even more easily.

Just near the settimeofday() syscall, similar foot shooting can be
accomplished directly using the adjkerntz sysctl.  Changes to this
normally update the RTC immediately.  This is correct if the changes
are correct.  Small garbage values give garbage in the RTC and large
garbage values give overflows.  Bounds checking in this commit sometimes
detects these overflows.

Foot-shooting can also be accomplished indirectly using adjkerntz.
Set it to a large (in magnitude) value that doesn't cause overflows.
This augments the overflows in the next call to settimeofday().

The fix is not to add bounds checking to all sysctls.  Even restricting
according to securelevel wouldn't work right, since securelevel doesn't
work right.  In sys/kern, there are only 10 sysctls (not includinging
adjkerntz) restricted by securelevel.  However, settimeofday() is
restricted by securelevel.  You can't set the time backwards at high
securelevels, but you can still set the RTC to anything using adjkerntz
at high securelevels.

settimeofday() gives another unsecured method with results similar to
adjkerntz for panicing and corrupting the RTC.  set/gettimeofday()
still support timezones, and resettodr() adds both adjkerntz and
(tz_minuteswest * 60) to convert the kernel time to the RTC time.  Both
of these additions are FreeBSD features.  adjkerntz is necessary to
support normal use of the RTC on x86 (it is on local time), but
timezones in the kernel were deprecated 25 years ago, so why use them
now?  On my systems, adjkerntz is always used and I never used
tz_minutewest until today to demonstrate foot shooting.  The man page
says that the timezone is no longer used; actually, it is used for this
foot shooting.

There are many bugs in the implementation of the foot-shooting:

A: There are no bounds check on tz_minuteswest, so expressions like
(tz_minuteswest * 60) may overflow before the foot is the target.
settimeofday() tries to be careful about the order of operations, but
there is no order that just works for the dependent operations of
setting the time and the timezone.  The new timezone should be set
before setting the time, since the timezone is needed for the RTC part
of setting the time, and settings should be reversible in case one
part fails.  In practice, the time is set first, and if this succeeds
then further errors are mostly not detected, but sometimes cause panics;
then the RTC is set using a non-atomic copy of the pair (adjkerntz,
tz_minuteswest), where tz_minuteswest was usually set by the previous
settimeofday() call.  Then tz_minuteswest is set for this call.

B: There is no interlocking for any of this.  adjkerntz and tz_minuteswest
are plain ints, so reading them is probably atomic, but any number of
adjkerntz sysctls and settimeofday() syscalls can race each other.
The adjkerntz sysctl claims to be MPSAFE, but does nothing special.
It just overwrites the adjkerntz using a hopefully-atomic access, then
calls resettodr() like settime().  kib added some locking around the
hardware part of resettodr(), and did more careful things for the
boottime offset for timecounters.  The boottime offset is more important
since it is used for every CLOCK_REALTIME timecounter call, and needs
atomic accesses more since it is larger than an int.

Some of the interlocking belongs in applications.  E.g., after setting
the time, read the time and the monotonic time to check that the
(monotonic) time taken to set the time was not so long as to make the
setting too out of date, and retry until it was not too out of date.
Coherency between separate settings of the time and the RTC could be
maintained in the same way, but since the kernel sets both it do the
check.  Granularity of the x86 RTC is 1 second, and accuracy is worse,
so a long lag is acceptable, but this is MD.  My version doesn't bother
doing RTC updates for differences of less than 2 seconds.

>>  Add additionally safety and overflow checks to clock_ts_to_ct and the
>>  BCD routines while we're here.
>
> I also disagreed with previous versions of this fix.
>
>>  Perform a safety check in sys_clock_settime() first to avoid easy local
>>  root panic, without having to propagate an error value back through
>>  dozens of APIs currently lacking error returns.
>
> I agree with not over-engineering this to check at all levels.  But top-level
> check needs to be more stringent and magic to work.  It is easier to check
> a couple of levels lower.

But there is no way for lower levels to report errors, so the up-front
check is of some use.  It mainly needs to be much stricter so that lower
levels can't fail.

> ...

I wrote a test program to demonstrate most of the panics reported previously,
and a couple of other problems:

X #include <sys/time.h>
X #include <limits.h>
X 
X int
X main(void)
X {
X 	struct timeval tv;
X 	struct timezone tz;
X 	int r;
X 
X 	r = gettimeofday(&tv, &tz);
X 	if (r != 0)
X 		err(1, "gettimeofday");
X 	tv.tv_usec = 0;
X #ifdef NEG
X 	tv.tv_sec = 0;
X 	/*
X 	 * The RTC time wants to be tv_sec - adjkerntz - tz_minuteswest.
X 	 * Make this go negative (and panic) by putting a positive value
X 	 * in tz_minuteswest large enough to kill any non-perverse value
X 	 * in adjkerntz.  We can get the panic (and lots of races) more
X 	 * easily using sysctl to put perverse values in adjkerntz before
X 	 * calling settimeofday.
X 	 */
X 	tz.tz_minuteswest = 24 * 60;	/* 1 day before 1970 */

The limit checking added to the bcd functions detects bugs from this
setting.

If the RTC is messed up by less preposterous settings, then the error is
detected on the next boot; then machdep.adjkerntz is set to 0 instead of
the correct value, and machdep.disable_rtc_set is set to 1 to disable
updating the RTC when adjkerntz is changed.  It takes settimeofday() or
the periodic update from an ntp-known-good value to update the RTC, and
these don't unset machdep.disable_rtc_set.

X #elif YETANOTHERBUG
X 	/*
X 	 * With 32-bit time_t, INT_MAX is accepted, but overflows 1 second
X 	 * later.  Userland then prints the year as 1901.  Userland does
X 	 * avoid overflow when adding a positive timezone offset to INT_MAX.
X 	 */
X 	tv.tv_sec = INT_MAX;
X 	tz.tz_minuteswest = 0;

This shows that times anywhere the limit must not be allowed.  The limit
should be about 2034 for 32-bit time_t.  The limit should be TIME_MAX,
but that doesn't exist.

X #elif defined(__i386__) /* XXX really 32-bit time_t */
X 	/*
X 	 * 32-bit time_t is easy to overflow to negative starting at any
X 	 * non-small nonnegative value.
X          */
X 	tv.tv_sec = INT_MAX;
X 	tz.tz_minuteswest = -24 * 60;	/* 1 day after end of 32-bit time_t */
X #else /* XXX really 64-bit time_t */
X 	/*
X 	 * Put the bogus upper value for the limit check in tv_sec to get a
X 	 * panic.  We don't even need to go ~1 day higher using,
X 	 * tz_minuteswest, since 366 is ~3/4 above the length of a year so
X 	 * so the limit is already ~7499 days above the 5-digit year 10000.
X 	 * Plus the Epoch bias of 1970 years.  1990.53 years altogether.
X 	 * The panic for this gives the precise value 1989 above 10000.
X 	 */
X 	tv.tv_sec = 9999L * 366 * 24 * 60 * 60;
X 	tz.tz_minuteswest = -24 * 60;
X #endif
X 	r = settimeofday(&tv, &tz);
X 	if (r != 0)
X 		err(1, "1st settimeofday");
X 	/*
X 	 * One of many bugs in settimeofday() is that its tz_minuteswest
X 	 * setting doesn't apply until the next call.  Make this call.
X 	 */
X 	r = settimeofday(&tv, &tz);
X 	if (r != 0)
X 		err(1, "2nd settimeofday");
X 	return (0);
X }

Bruce


More information about the svn-src-head mailing list