diff --git a/include/time.h b/include/time.h --- a/include/time.h +++ b/include/time.h @@ -117,6 +117,7 @@ #define CLOCK_SECOND 13 /* FreeBSD-specific. */ #define CLOCK_THREAD_CPUTIME_ID 14 #define CLOCK_PROCESS_CPUTIME_ID 15 +/* CLOCK_ADJUST_REALTIME 16 is FreeBSD-private */ #endif /* !defined(CLOCK_MONOTONIC) && __POSIX_VISIBLE >= 200112 */ #if __BSD_VISIBLE diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc --- a/lib/libc/gen/Makefile.inc +++ b/lib/libc/gen/Makefile.inc @@ -28,6 +28,7 @@ check_utility_compat.c \ clock.c \ clock_getcpuclockid.c \ + clock_settime_adjust.c \ closedir.c \ confstr.c \ crypt.c \ diff --git a/lib/libc/gen/Symbol.map b/lib/libc/gen/Symbol.map --- a/lib/libc/gen/Symbol.map +++ b/lib/libc/gen/Symbol.map @@ -422,6 +422,7 @@ }; FBSD_1.6 { + clock_settime_adjust; getlogin_r; memalign; scandir_b; diff --git a/lib/libc/gen/clock_settime_adjust.c b/lib/libc/gen/clock_settime_adjust.c new file mode 100644 --- /dev/null +++ b/lib/libc/gen/clock_settime_adjust.c @@ -0,0 +1,49 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 The FreeBSD Foundation + * + * Portions of this software were developed by Konstantin Belousov + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +int +clock_settime_adjust(const struct timespec *adj, struct timespec *new) +{ + struct timespec adj1; + int error; + + adj1 = *adj; + error == syscall(SYS_clock_settime, CLOCK_ADJUST_REALTIME, &adj1); + if (error == 0 && new != NULL) + *new = adj1; + return (error); +} diff --git a/sys/compat/freebsd32/freebsd32_misc.c b/sys/compat/freebsd32/freebsd32_misc.c --- a/sys/compat/freebsd32/freebsd32_misc.c +++ b/sys/compat/freebsd32/freebsd32_misc.c @@ -2919,17 +2919,25 @@ 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 && 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 diff --git a/sys/compat/linux/linux_time.c b/sys/compat/linux/linux_time.c --- a/sys/compat/linux/linux_time.c +++ b/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); diff --git a/sys/dev/hyperv/utilities/vmbus_timesync.c b/sys/dev/hyperv/utilities/vmbus_timesync.c --- a/sys/dev/hyperv/utilities/vmbus_timesync.c +++ b/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; diff --git a/sys/kern/kern_tc.c b/sys/kern/kern_tc.c --- a/sys/kern/kern_tc.c +++ b/sys/kern/kern_tc.c @@ -1245,7 +1245,7 @@ * when we booted. */ void -tc_setclock(struct timespec *ts) +tc_setclock(struct timespec *ts, bool abs) { struct timespec tbef, taft; struct bintime bt, bt2; @@ -1254,10 +1254,14 @@ 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... + */ tc_windup(&bt); mtx_unlock_spin(&tc_setclock_mtx); diff --git a/sys/kern/kern_time.c b/sys/kern/kern_time.c --- a/sys/kern/kern_time.c +++ b/sys/kern/kern_time.c @@ -86,6 +86,8 @@ */ static int settime(struct thread *, struct timeval *); +static int steplimit(struct thread *td, struct timeval *tv, bool abs); +static void steptime(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, @@ -113,59 +115,136 @@ #define CLOCK_CALL(clock, call, arglist) \ ((*posix_clocks[clock].call) arglist) +static SYSCTL_NODE(_kern, OID_AUTO, settime, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "Controls for settime"); + +static int allow_insane_settime = 0; +SYSCTL_INT(_kern_settime, OID_AUTO, allow_insane_settime, CTLFLAG_RWTUN, + &allow_insane_settime, 0, + "do not perform possibly restrictive checks on settime(2) args"); +static u_long insane_settime_val = 8000; +SYSCTL_ULONG(_kern_settime, OID_AUTO, insane_settime_val, CTLFLAG_RWTUN, + &insane_settime_val, 0, + "insane years for clock_settime(2)"); +static int secure_steplimit = 1; +SYSCTL_INT(_kern_settime, OID_AUTO, secure_steplimit, CTLFLAG_RWTUN, + &secure_steplimit, 0, + "limit for step on securelevel > 1, in seconds"); +/* + * Default limit it set to 12h to allow adjustment to fix local-zone RTC + * without requiring tuning. + */ +static int insane_adjust_limit = 12 * 3600; +SYSCTL_INT(_kern_settime, OID_AUTO, insane_adjust_limit, CTLFLAG_RWTUN, + &insane_adjust_limit, 0, + "insane seconds for CLOCK_ADJUST_REALTIME"); + SYSINIT(posix_timer, SI_SUB_P1003_1B, SI_ORDER_FIRST+4, itimer_start, NULL); +static struct timeval maxtime, laststep; +static struct mtx sl_lock; +MTX_SYSINIT(sl_lock, &sl_lock, "sll", MTX_DEF); + static int -settime(struct thread *td, struct timeval *tv) +steplimit(struct thread *td, struct timeval *tv, bool abs) { struct timeval delta, tv1, tv2; - static struct timeval maxtime, laststep; - struct timespec ts; + int error, sl; + + if (securelevel_gt(td->td_ucred, 1) == 0) + return (0); + error = 0; + mtx_lock(&sl_lock); microtime(&tv1); delta = *tv; - timevalsub(&delta, &tv1); + if (abs) + timevalsub(&delta, &tv1); + + sl = secure_steplimit; /* - * If the system is secure, we do not allow the time to be - * set to a value earlier than 1 second less than the highest - * time we have yet seen. The worst a miscreant can do in - * this circumstance is "freeze" time. He couldn't go - * back to the past. - * - * We similarly do not allow the clock to be stepped more - * than one second, nor more than once per second. This allows - * a miscreant to make the clock march double-time, but no worse. + * If the system is secure, we do not allow the time to be set + * to a value earlier than secure_steplimit (defaulted to 1 + * second) less than the highest time we have yet seen. The + * worst a miscreant can do in this circumstance is "freeze" + * time. He couldn't go back to the past. */ - if (securelevel_gt(td->td_ucred, 1) != 0) { - if (delta.tv_sec < 0 || delta.tv_usec < 0) { - /* - * Update maxtime to latest time we've seen. - */ - if (tv1.tv_sec > maxtime.tv_sec) - maxtime = tv1; + if (delta.tv_sec < 0 || delta.tv_usec < 0) { + /* + * Update maxtime to latest time we've seen. + */ + if (tv1.tv_sec > maxtime.tv_sec) + maxtime = tv1; + if (abs) { tv2 = *tv; timevalsub(&tv2, &maxtime); - if (tv2.tv_sec < -1) { - tv->tv_sec = maxtime.tv_sec - 1; - printf("Time adjustment clamped to -1 second\n"); + if (tv2.tv_sec < -sl) { + tv->tv_sec = maxtime.tv_sec - sl; + if (bootverbose) { + printf( + "Time adjustment clamped to -%d second\n", + sl); + } } } else { - if (tv1.tv_sec == laststep.tv_sec) - return (EPERM); - if (delta.tv_sec > 1) { - tv->tv_sec = tv1.tv_sec + 1; - printf("Time adjustment clamped to +1 second\n"); + if (delta.tv_sec < -sl) { + tv->tv_sec = - sl; + if (bootverbose) { + printf( + "Time adjustment clamped to -%d second\n", + sl); + } } - laststep = *tv; } + goto out; + } + + /* + * We similarly do not allow the clock to be stepped more than + * one second, nor more than secure_steplimit (by default, + * once per second). This allows a miscreant to make the + * clock march double-time, but no worse. + */ + if (tv1.tv_sec == laststep.tv_sec) { + error = EPERM; + goto out; + } + if (delta.tv_sec > sl) { + tv->tv_sec = abs ? tv1.tv_sec + sl : sl; + printf("Time adjustment clamped to +%d second\n", sl); } + if (abs) { + laststep = *tv; + } else { + timevaladd(&tv1, &delta); + laststep = tv1; + } +out: + mtx_unlock(&sl_lock); + return (error); +} + +static int +settime(struct thread *td, struct timeval *tv) +{ + int error; + + error = steplimit(td, tv, true); + if (error == 0) + steptime(tv, true); + return (error); +} + +static void +steptime(struct timeval *tv, bool abs) +{ + struct timespec ts; ts.tv_sec = tv->tv_sec; ts.tv_nsec = tv->tv_usec * 1000; - tc_setclock(&ts); + tc_setclock(&ts, abs); resettodr(); - return (0); } #ifndef _SYS_SYSPROTO_H_ @@ -384,39 +463,56 @@ 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 && uap->clock_id == CLOCK_ADJUST_REALTIME) + error = copyout(&ots, __DECONST(void *, uap->tp), sizeof(ots)); + return (error); } -static int allow_insane_settime = 0; -SYSCTL_INT(_debug, OID_AUTO, allow_insane_settime, CTLFLAG_RWTUN, - &allow_insane_settime, 0, - "do not perform possibly restrictive checks on settime(2) args"); - 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; 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) + break; + if (!allow_insane_settime && + (ats->tv_sec > insane_adjust_limit || + ats->tv_sec < -insane_adjust_limit)) + break; + TIMESPEC_TO_TIMEVAL(&atv, ats); + error = steplimit(td, &atv, false); + if (error != 0) + break; + steptime(&atv, false); + if (ots != NULL) + TIMEVAL_TO_TIMESPEC(&atv, 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 > insane_settime_val * 365ULL * 24 * 60 * 60 || + ats->tv_sec < utc_offset())) + break; + /* XXX Don't convert nsec->usec and back */ + TIMESPEC_TO_TIMEVAL(&atv, ats); + error = settime(td, &atv); + break; + } return (error); } diff --git a/sys/kern/subr_rtc.c b/sys/kern/subr_rtc.c --- a/sys/kern/subr_rtc.c +++ b/sys/kern/subr_rtc.c @@ -361,7 +361,7 @@ } if (ts.tv_sec >= 0) { - tc_setclock(&ts); + tc_setclock(&ts, true); #ifdef FFCLOCK ffclock_reset_clock(&ts); #endif diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h --- a/sys/sys/syscallsubr.h +++ b/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); diff --git a/sys/sys/time.h b/sys/sys/time.h --- a/sys/sys/time.h +++ b/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 @@ -613,6 +614,7 @@ #if __BSD_VISIBLE int adjtime(const struct timeval *, struct timeval *); int clock_getcpuclockid2(id_t, int, clockid_t *); +int clock_settime_adjust(const struct timespec *, struct timespec *); int futimes(int, const struct timeval *); int futimesat(int, const char *, const struct timeval [2]); int lutimes(const char *, const struct timeval *); diff --git a/sys/sys/timetc.h b/sys/sys/timetc.h --- a/sys/sys/timetc.h +++ b/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, bool abs); void tc_ticktock(int cnt); void cpu_tick_calibration(void);