Index: sys/compat/freebsd32/freebsd32_misc.c =================================================================== --- sys/compat/freebsd32/freebsd32_misc.c +++ sys/compat/freebsd32/freebsd32_misc.c @@ -2919,17 +2919,27 @@ freebsd32_clock_settime(struct thread *td, struct freebsd32_clock_settime_args *uap) { - struct timespec ats; - struct timespec32 ats32; + struct timespec ats, ots; + struct timespec32 ats32, ots32; int error; error = copyin(uap->tp, &ats32, sizeof(ats32)); - if (error) + if (error != 0) return (error); CP(ats32, ats, tv_sec); CP(ats32, ats, tv_nsec); - return (kern_clock_settime(td, uap->clock_id, &ats)); + error = kern_clock_settime(td, uap->clock_id, &ats, &ots); + if (error != 0) + return (error); + + if (uap->clock_id == CLOCK_ADJUST_REALTIME) { + CP(ots, ots32, tv_sec); + CP(ots, ots32, tv_nsec); + error = copyout(&ots32, __DECONST(void *, uap->tp), + sizeof(ots32)); + } + return (error); } int Index: sys/compat/linux/linux_time.c =================================================================== --- sys/compat/linux/linux_time.c +++ sys/compat/linux/linux_time.c @@ -422,7 +422,7 @@ return (error); } - error = kern_clock_settime(td, nwhich, &ts); + error = kern_clock_settime(td, nwhich, &ts, NULL); if (error != 0) LIN_SDT_PROBE1(time, linux_clock_settime, settime_error, error); Index: sys/dev/hyperv/utilities/vmbus_timesync.c =================================================================== --- sys/dev/hyperv/utilities/vmbus_timesync.c +++ sys/dev/hyperv/utilities/vmbus_timesync.c @@ -132,7 +132,7 @@ } hv_ts.tv_sec = hv_ns / NANOSEC; hv_ts.tv_nsec = hv_ns % NANOSEC; - kern_clock_settime(curthread, CLOCK_REALTIME, &hv_ts); + kern_clock_settime(curthread, CLOCK_REALTIME, &hv_ts, NULL); /* Done! */ return; } @@ -164,7 +164,8 @@ } hv_ts.tv_sec = hv_ns / NANOSEC; hv_ts.tv_nsec = hv_ns % NANOSEC; - kern_clock_settime(curthread, CLOCK_REALTIME, &hv_ts); + kern_clock_settime(curthread, CLOCK_REALTIME, &hv_ts, + NULL); } /* Done */ return; Index: sys/kern/kern_tc.c =================================================================== --- sys/kern/kern_tc.c +++ sys/kern/kern_tc.c @@ -1245,7 +1245,7 @@ * when we booted. */ void -tc_setclock(struct timespec *ts) +tc_setclock(struct timespec *ts, struct timespec *ots, bool abs) { struct timespec tbef, taft; struct bintime bt, bt2; @@ -1254,11 +1254,19 @@ nanotime(&tbef); mtx_lock_spin(&tc_setclock_mtx); cpu_tick_calibrate(1); - binuptime(&bt2); - bintime_sub(&bt, &bt2); + if (abs) { + binuptime(&bt2); + bintime_sub(&bt, &bt2); + } - /* XXX fiddle all the little crinkly bits around the fiords... */ + /* + * Fiddle all the little crinkly bits around the fiords... + * If requested, return the time after adjustment, under the + * tc_setclock spinlock to be as close to result as possible. + */ tc_windup(&bt); + if (ots != NULL) + getnanotime(ots); mtx_unlock_spin(&tc_setclock_mtx); /* Avoid rtc_generation == 0, since td_rtcgen == 0 is special. */ Index: sys/kern/kern_time.c =================================================================== --- sys/kern/kern_time.c +++ sys/kern/kern_time.c @@ -86,6 +86,8 @@ */ static int settime(struct thread *, struct timeval *); +static int steptime(struct thread *, struct timeval *, struct timeval *, + bool abs); static void timevalfix(struct timeval *); static int user_clock_nanosleep(struct thread *td, clockid_t clock_id, int flags, const struct timespec *ua_rqtp, @@ -120,7 +122,6 @@ { struct timeval delta, tv1, tv2; static struct timeval maxtime, laststep; - struct timespec ts; microtime(&tv1); delta = *tv; @@ -161,10 +162,22 @@ } } + return (steptime(td, tv, NULL, true)); +} + +static int +steptime(struct thread *td, struct timeval *tv, struct timeval *otv, bool abs) +{ + struct timespec ts, ots; + ts.tv_sec = tv->tv_sec; ts.tv_nsec = tv->tv_usec * 1000; - tc_setclock(&ts); + tc_setclock(&ts, &ots, abs); resettodr(); + if (otv != NULL) { + otv->tv_sec = ots.tv_sec; + otv->tv_usec = ots.tv_nsec / 1000; + } return (0); } @@ -384,12 +397,17 @@ int sys_clock_settime(struct thread *td, struct clock_settime_args *uap) { - struct timespec ats; + struct timespec ats, ots; int error; if ((error = copyin(uap->tp, &ats, sizeof(ats))) != 0) return (error); - return (kern_clock_settime(td, uap->clock_id, &ats)); + error = kern_clock_settime(td, uap->clock_id, &ats, &ots); + if (error != 0) + return (error); + if (uap->clock_id == CLOCK_ADJUST_REALTIME) + error = copyout(&ots, __DECONST(void *, uap->tp), sizeof(ots)); + return (error); } static int allow_insane_settime = 0; @@ -397,26 +415,47 @@ &allow_insane_settime, 0, "do not perform possibly restrictive checks on settime(2) args"); +/* + * Arbitrary limit to prevent insane + * clock_settime(CLOCK_ADJUST_REALTIME), e.g. with a garbage ts. + */ +#define CLOCK_ADJUST_REALTIME_LIMIT 3600 + int -kern_clock_settime(struct thread *td, clockid_t clock_id, struct timespec *ats) +kern_clock_settime(struct thread *td, clockid_t clock_id, struct timespec *ats, + struct timespec *ots) { - struct timeval atv; + struct timeval atv, oatv; int error; if ((error = priv_check(td, PRIV_CLOCK_SETTIME)) != 0) return (error); - if (clock_id != CLOCK_REALTIME) - return (EINVAL); - if (ats->tv_nsec < 0 || ats->tv_nsec >= 1000000000 || - ats->tv_sec < 0) - return (EINVAL); - if (!allow_insane_settime && - (ats->tv_sec > 8000ULL * 365 * 24 * 60 * 60 || - ats->tv_sec < utc_offset())) - return (EINVAL); - /* XXX Don't convert nsec->usec and back */ - TIMESPEC_TO_TIMEVAL(&atv, ats); - error = settime(td, &atv); + error = EINVAL; + switch (clock_id) { + case CLOCK_ADJUST_REALTIME: + if (ats->tv_nsec < 0 || ats->tv_nsec >= 1000000000 || + (!allow_insane_settime && + (ats->tv_sec > CLOCK_ADJUST_REALTIME_LIMIT || + ats->tv_sec < -CLOCK_ADJUST_REALTIME_LIMIT))) + break; + TIMESPEC_TO_TIMEVAL(&atv, ats); + error = steptime(td, &atv, &oatv, false); + if (ots != NULL) + TIMEVAL_TO_TIMESPEC(&oatv, ots); + break; + case CLOCK_REALTIME: + if (ats->tv_nsec < 0 || ats->tv_nsec >= 1000000000 || + ats->tv_sec < 0) + break; + if (!allow_insane_settime && + (ats->tv_sec > 8000ULL * 365 * 24 * 60 * 60 || + ats->tv_sec < utc_offset())) + error = EINVAL; + /* XXX Don't convert nsec->usec and back */ + TIMESPEC_TO_TIMEVAL(&atv, ats); + error = settime(td, &atv); + break; + } return (error); } Index: sys/kern/subr_rtc.c =================================================================== --- sys/kern/subr_rtc.c +++ sys/kern/subr_rtc.c @@ -361,7 +361,7 @@ } if (ts.tv_sec >= 0) { - tc_setclock(&ts); + tc_setclock(&ts, NULL, true); #ifdef FFCLOCK ffclock_reset_clock(&ts); #endif Index: sys/sys/syscallsubr.h =================================================================== --- sys/sys/syscallsubr.h +++ sys/sys/syscallsubr.h @@ -105,7 +105,7 @@ int kern_clock_nanosleep(struct thread *td, clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp); int kern_clock_settime(struct thread *td, clockid_t clock_id, - struct timespec *ats); + struct timespec *ats, struct timespec *ots); void kern_thread_cputime(struct thread *targettd, struct timespec *ats); void kern_process_cputime(struct proc *targetp, struct timespec *ats); int kern_close_range(struct thread *td, u_int lowfd, u_int highfd); Index: sys/sys/time.h =================================================================== --- sys/sys/time.h +++ sys/sys/time.h @@ -480,6 +480,7 @@ #define CLOCK_SECOND 13 /* FreeBSD-specific. */ #define CLOCK_THREAD_CPUTIME_ID 14 #define CLOCK_PROCESS_CPUTIME_ID 15 +#define CLOCK_ADJUST_REALTIME 16 /* FreeBSD-specific. */ #endif #ifndef TIMER_ABSTIME Index: sys/sys/timetc.h =================================================================== --- sys/sys/timetc.h +++ sys/sys/timetc.h @@ -88,7 +88,7 @@ u_int64_t tc_getfrequency(void); void tc_init(struct timecounter *tc); -void tc_setclock(struct timespec *ts); +void tc_setclock(struct timespec *ts, struct timespec *ots, bool abs); void tc_ticktock(int cnt); void cpu_tick_calibration(void);