What will chrony run on?
========================
-The software is known to work on Linux, FreeBSD, NetBSD, macOS and
-illumos. Closely related systems may work too. Any other system will
-likely require a porting exercise.
+The software is known to work on Linux, FreeBSD, NetBSD, OpenBSD, macOS,
+and illumos. Closely related systems may work too. Any other system
+will likely require a porting exercise.
How do I set it up?
===================
try_clockctl=0
feat_scfilter=0
try_seccomp=-1
+try_pledge=0
priv_ops=""
feat_ipv6=1
feat_phc=1
add_def NETBSD
echo "Configuring for $SYSTEM"
;;
+ OpenBSD)
+ EXTRA_OBJECTS="sys_generic.o sys_openbsd.o sys_posix.o"
+ try_setsched=1
+ try_lockmem=1
+ try_pledge=1
+ add_def OPENBSD
+ if [ $feat_droproot = "1" ]; then
+ add_def FEAT_PRIVDROP
+ priv_ops="ADJUSTTIME ADJUSTFREQ SETTIME"
+ fi
+ echo "Configuring for $SYSTEM"
+ ;;
Darwin)
EXTRA_OBJECTS="sys_macosx.o"
LIBS="$LIBS -lresolv"
EXTRA_OBJECTS="$EXTRA_OBJECTS sys_linux_scmp.o"
fi
+if [ $feat_scfilter = "1" ] && [ $try_pledge = "1" ] && \
+ test_code 'pledge()' 'unistd.h' '' '' 'pledge("stdio", NULL);'
+then
+ add_def FEAT_SCFILTER
+fi
+
if [ "x$priv_ops" != "x" ]; then
EXTRA_OBJECTS="$EXTRA_OBJECTS privops.o"
add_def PRIVOPS_HELPER
This directive specifies the maximum assumed drift (frequency error) of the
system clock. It limits the frequency adjustment that *chronyd* is allowed to
use to correct the measured drift. It is an additional limit to the maximum
-adjustment that can be set by the system driver (100000 ppm on Linux, 500 ppm
-on FreeBSD, NetBSD, and macOS 10.13+, 32500 ppm on illumos).
+adjustment that can be set by the system driver (100000 ppm on Linux and
+OpenBSD, 500 ppm on FreeBSD, NetBSD, and macOS 10.13+, 32500 ppm on illumos).
+
By default, the maximum assumed drift is 500000 ppm, i.e. the adjustment is
limited by the system driver rather than this directive.
all supported systems with the exception of macOS 12 or earlier).
+
For each system there is a maximum frequency offset of the clock that can be set
-by the driver. On Linux it is 100000 ppm, on FreeBSD, NetBSD and macOS 10.13+ it
-is 5000 ppm, and on illumos it is 32500 ppm. Also, due to a kernel limitation,
-setting *maxslewrate* on FreeBSD, NetBSD, macOS 10.13+ to a value between 500
-ppm and 5000 ppm will effectively set it to 500 ppm.
+by the driver. On Linux and OpenBSD it is 100000 ppm, on FreeBSD, NetBSD and
+macOS 10.13+ it is 5000 ppm, on illumos it is 32500 ppm. Also, due to a kernel
+limitation, setting *maxslewrate* on FreeBSD, NetBSD, macOS 10.13+ to a value
+between 500 ppm and 5000 ppm will effectively set it to 500 ppm.
+
By default, the maximum slew rate is set to 83333.333 ppm (one twelfth).
[[lock_all]]*lock_all*::
The *lock_all* directive will lock the *chronyd* process into RAM so that it
will never be paged out. This can result in lower and more consistent latency.
-The directive is supported on Linux, FreeBSD, NetBSD, and illumos.
+The directive is supported on Linux, FreeBSD, NetBSD, OpenBSD, and illumos.
[[pidfile]]*pidfile* _file_::
Unless *chronyd* is started with the *-Q* option, it writes its process ID
The default is 123, the minimum is 0, and the maximum is 255.
[[sched_priority]]*sched_priority* _priority_::
-On Linux, FreeBSD, NetBSD, and illumos, the *sched_priority* directive will
-select the SCHED_FIFO real-time scheduler at the specified priority (which must
-be between 0 and 100). On macOS, this option must have either a value of 0 (the
-default) to disable the thread time constraint policy or 1 for the policy to be
-enabled.
+On Linux, FreeBSD, NetBSD, OpenBSD, and illumos, the *sched_priority* directive
+will select the SCHED_FIFO real-time scheduler at the specified priority (which
+must be between 0 and 100). On macOS, this option must have either a value of 0
+(the default) to disable the thread time constraint policy or 1 for the policy
+to be enabled.
+
On systems other than macOS, this directive uses the *pthread_setschedparam()*
system call to instruct the kernel to use the SCHED_FIFO first-in, first-out
switch after start in order to drop root privileges.
+
On Linux, *chronyd* needs to be compiled with support for the *libcap* library.
-On macOS, FreeBSD, NetBSD and illumos *chronyd* forks into two processes.
-The child process retains root privileges, but can only perform a very limited
-range of privileged system calls on behalf of the parent.
+On macOS, FreeBSD, NetBSD, OpenBSD, and illumos *chronyd* forks into two
+processes. The child process retains root privileges, but can only perform a
+very limited range of privileged system calls on behalf of the parent.
+
The compiled-in default value is _@DEFAULT_USER@_.
and restart *chronyd* briefly for any reason, e.g. to install a new version.
However, it should be used only on systems where the kernel can maintain clock
compensation whilst not under *chronyd*'s control (i.e. Linux, FreeBSD, NetBSD,
-illumos, and macOS 10.13 or later).
+OpenBSD, illumos, and macOS 10.13 or later).
*-R*::
When this option is used, the <<chrony.conf.adoc#initstepslew,*initstepslew*>>
_@DEFAULT_USER@_.
+
On Linux, *chronyd* needs to be compiled with support for the *libcap* library.
-On macOS, FreeBSD, NetBSD, and illumos *chronyd* forks into two processes.
-The child process retains root privileges, but can only perform a very limited
-range of privileged system calls on behalf of the parent.
+On macOS, FreeBSD, NetBSD, OpenBSD, and illumos *chronyd* forks into two
+processes. The child process retains root privileges, but can only perform a
+very limited range of privileged system calls on behalf of the parent.
*-U*::
This option disables a check for root privileges to allow *chronyd* to be
*-F* _level_::
This option configures system call filters loaded by *chronyd* processes if it
-was compiled with support for the Linux secure computing (seccomp) facility.
-Three levels are defined: 0, 1, 2. The filters are disabled at level 0. At
-levels 1 and 2, *chronyd* will be killed if it makes a system call which is
-blocked by the filters. The level can be specified as a negative number to
-trigger the SIGSYS signal instead of SIGKILL, which can be useful for
-debugging. The default value is 0.
+was compiled with support for the Linux secure computing (seccomp) facility or
+OpenBSD pledge. For Linux three levels are defined: 0, 1, 2, for OpenBSD two
+levels: 0, 1. The filters are disabled at level 0. On Linux at levels 1 and 2
+and on OpenBSD at level 1, *chronyd* will be killed if it makes a system call
+which is blocked by the filters. On Linux the level can be specified as a
+negative number to trigger the SIGSYS signal instead of SIGKILL, which can be
+useful for debugging. The default value is 0.
+
-At level 1, the filters allow only selected system calls that are normally
-expected to be made by *chronyd*. Other system calls are blocked. This level is
-recommended only if it is known to work on the version of the system where
-*chrony* is installed. The filters need to allow also system calls made by
-libraries that *chronyd* is using (e.g. libc), but different versions or
-implementations of the libraries might make different system calls. If the
-filters are missing a system call, *chronyd* could be killed even in normal
-operation.
+On Linux at level 1, the filters allow only selected system calls that are
+normally expected to be made by *chronyd*. Other system calls are blocked.
+This level is recommended only if it is known to work on the version of the
+system where *chrony* is installed. The filters need to allow also system
+calls made by libraries that *chronyd* is using (e.g. libc), but different
+versions or implementations of the libraries might make different system calls.
+If the filters are missing a system call, *chronyd* could be killed even in
+normal operation.
+
At level 2, the filters block only a small number of specific system calls
(e.g. fork and exec). This approach should avoid false positives, but the
The filters cannot be enabled with the *mailonchange* directive.
*-P* _priority_::
-On Linux, FreeBSD, NetBSD, and illumos this option will select the SCHED_FIFO
-real-time scheduler at the specified priority (which must be between 0 and
-100). On macOS, this option must have either a value of 0 to disable the thread
-time constraint policy or 1 for the policy to be enabled. Other systems do not
-support this option. The default value is 0.
+On Linux, FreeBSD, NetBSD, OpenBSD, and illumos this option will select the
+SCHED_FIFO real-time scheduler at the specified priority (which must be
+between 0 and 100). On macOS, this option must have either a value of 0 to
+disable the thread time constraint policy or 1 for the policy to be enabled.
+Other systems do not support this option. The default value is 0.
*-m*::
This option will lock *chronyd* into RAM so that it will never be paged out.
-This mode is only supported on Linux, FreeBSD, NetBSD, and illumos.
+This mode is only supported on Linux, FreeBSD, NetBSD, OpenBSD, and illumos.
*-x*::
This option disables the control of the system clock. *chronyd* will not try to
#elif defined(NETBSD) || defined(FREEBSD)
#include "sys_netbsd.h"
#include "sys_posix.h"
+#elif defined(OPENBSD)
+#include "sys_openbsd.h"
+#include "sys_posix.h"
#elif defined(MACOSX)
#include "sys_macosx.h"
#endif
SYS_Solaris_Initialise();
#elif defined(NETBSD) || defined(FREEBSD)
SYS_NetBSD_Initialise();
+#elif defined(OPENBSD)
+ SYS_OpenBSD_Initialise();
#elif defined(MACOSX)
SYS_MacOSX_Initialise();
#else
SYS_Solaris_Finalise();
#elif defined(NETBSD) || defined(FREEBSD)
SYS_NetBSD_Finalise();
+#elif defined(OPENBSD)
+ SYS_OpenBSD_Finalise();
#elif defined(MACOSX)
SYS_MacOSX_Finalise();
#else
SYS_Solaris_DropRoot(uid, gid, context);
#elif (defined(NETBSD) || defined(FREEBSD)) && defined(FEAT_PRIVDROP)
SYS_NetBSD_DropRoot(uid, gid, context, !null_driver);
+#elif defined(OPENBSD) && defined(FEAT_PRIVDROP)
+ SYS_OpenBSD_DropRoot(uid, gid, context, !null_driver);
#elif defined(MACOSX) && defined(FEAT_PRIVDROP)
SYS_MacOSX_DropRoot(uid, gid, context);
#else
{
#if defined(LINUX) && defined(FEAT_SCFILTER)
SYS_Linux_EnableSystemCallFilter(level, context);
+#elif defined(OPENBSD) && defined(FEAT_SCFILTER)
+ SYS_OpenBSD_EnableSystemCallFilter(level, context);
#else
LOG_FATAL("system call filter not supported");
#endif
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2001
+ * Copyright (C) J. Hannken-Illjes 2001
+ * Copyright (C) Miroslav Lichvar 2015
+ * Copyright (C) Shaun Ren 2021
+ * Copyright (C) Thomas Kupper 2026
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Driver file for the OpenBSD operating system.
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <sys/sysctl.h>
+
+#include "sys_generic.h"
+#include "sys_openbsd.h"
+#include "conf.h"
+#include "local.h"
+#include "logging.h"
+#include "privops.h"
+#include "sched.h"
+#include "util.h"
+
+/* The OpenBSD kernel supports a maximum value of 500000 ppm.
+ To avoid extending the range that would need to be tested, use
+ the same maximum as on Linux.
+
+ Maximum frequency offset (in ppm) */
+#define MAX_FREQ 100000.0
+
+/* RTC synchronisation - once an hour */
+
+static struct timespec last_rtc_sync;
+#define RTC_SYNC_INTERVAL (60 * 60.0)
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+ int64_t freq;
+
+ if (PRV_AdjustFreq(NULL, &freq))
+ LOG_FATAL("adjfreq() failed");
+
+ return (double)-freq / (1000LL << 32);
+}
+
+/* ================================================== */
+
+static double
+set_frequency(double freq_ppm)
+{
+ int64_t freq;
+
+ freq = -freq_ppm * (1000LL << 32);
+ if (PRV_AdjustFreq(&freq, NULL))
+ LOG_FATAL("adjfreq() failed");
+
+ return read_frequency();
+}
+
+/* ================================================== */
+
+static void
+synchronise_rtc(void)
+{
+ struct timespec ts, new_ts;
+ double err;
+
+ LCL_ReadRawTime(&ts);
+
+ if (PRV_SetTime(CLOCK_REALTIME, &ts) < 0) {
+ DEBUG_LOG("clock_settime() failed");
+ return;
+ }
+
+ LCL_ReadRawTime(&new_ts);
+ err = UTI_DiffTimespecsToDouble(&new_ts, &ts);
+
+ lcl_InvokeDispersionNotifyHandlers(fabs(err));
+}
+
+/* ================================================== */
+
+static void
+set_sync_status(int synchronised, double est_error, double max_error)
+{
+ double rtc_sync_elapsed;
+ struct timespec now;
+
+ if (!synchronised || !CNF_GetRtcSync())
+ return;
+
+ SCH_GetLastEventTime(NULL, NULL, &now);
+ rtc_sync_elapsed = UTI_DiffTimespecsToDouble(&now, &last_rtc_sync);
+ if (fabs(rtc_sync_elapsed) >= RTC_SYNC_INTERVAL) {
+ synchronise_rtc();
+ last_rtc_sync = now;
+ DEBUG_LOG("rtc synchronised");
+ }
+}
+
+
+/* ================================================== */
+
+static struct clockinfo
+get_clockinfo(void)
+{
+ struct clockinfo cinfo;
+ size_t cinfo_len;
+ int mib[2];
+
+ cinfo_len = sizeof (cinfo);
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_CLOCKRATE;
+
+ if (sysctl(mib, 2, &cinfo, &cinfo_len, NULL, 0) < 0)
+ LOG_FATAL("sysctl() failed");
+
+ return cinfo;
+}
+
+/* ================================================== */
+
+static void
+reset_adjtime_offset(void)
+{
+ struct timeval delta;
+
+ memset(&delta, 0, sizeof (delta));
+
+ if (PRV_AdjustTime(&delta, NULL))
+ LOG_FATAL("adjtime() failed");
+}
+
+/* ================================================== */
+
+/* PRV_SetTime() uses clock_settime() to set the system time.
+ clock_setsetime() on OpenBSD is not pledged but
+ settimeofday() is. Override clock_settime() here for
+ OpenBSD and call settimeofday() from it. */
+
+int
+clock_settime(clockid_t clock, const struct timespec *now)
+{
+ struct timeval tv;
+
+ if (clock != CLOCK_REALTIME)
+ return -1;
+
+ UTI_TimespecToTimeval(now, &tv);
+
+ return settimeofday(&tv, NULL);
+}
+
+/* ================================================== */
+
+void
+SYS_OpenBSD_Initialise(void)
+{
+ struct clockinfo cinfo;
+
+ cinfo = get_clockinfo();
+ reset_adjtime_offset();
+
+ LCL_ReadRawTime(&last_rtc_sync);
+
+ SYS_Generic_CompleteFreqDriver(MAX_FREQ, 1.0 / cinfo.hz,
+ read_frequency, set_frequency, NULL,
+ 0.0, 0.0,
+ NULL, NULL,
+ NULL, set_sync_status);
+}
+
+/* ================================================== */
+
+void
+SYS_OpenBSD_Finalise(void)
+{
+ SYS_Generic_Finalise();
+}
+
+/* ================================================== */
+
+#ifdef FEAT_PRIVDROP
+void
+SYS_OpenBSD_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context, int clock_control)
+{
+ if (context == SYS_MAIN_PROCESS)
+ PRV_StartHelper();
+
+ UTI_DropRoot(uid, gid);
+}
+#endif
+
+/* ================================================== */
+
+#ifdef FEAT_SCFILTER
+void
+SYS_OpenBSD_EnableSystemCallFilter(int level, SYS_ProcessContext context)
+{
+ /* If level == 0, SYS_EnableSystemCallFilter() is not called. Therefore
+ only a value of 1 is valid here. */
+ if (level != 1 && context == SYS_MAIN_PROCESS)
+ /* Only log/fatal once in the main process, the child processes will be
+ terminated too as a result */
+ LOG_FATAL("Unsupported filter level");
+
+ if (context == SYS_MAIN_PROCESS) {
+ /* stdio => allow libc stdio calls
+ {r,w,c}path => allow read/write/change config, drift file, etc
+ inet => allow connections to/from internet
+ unix => allow handling unix sockets
+ dns => allow DNS resolution
+ sendfd => allow send fd to nts_ke helper thread. In
+ NKS_Initialize() open_socket() -> accept_connection()
+ settime => allow set time if system call filter is enabled and user is root */
+ const char **certs, **keys;
+
+ if (CNF_GetNtsServerCertAndKeyFiles(&certs, &keys) > 0 &&
+ CNF_GetNtsServerProcesses() > 0) {
+ /* NTS-KE helper(s) will be forked, the 'sendfd' promise is necessary */
+ if (geteuid() == 0) {
+ /* Running as root, in addition settime pledge promise needed */
+ if (pledge("stdio rpath wpath cpath inet unix dns sendfd settime", NULL) < 0)
+ LOG_FATAL("pledge() failed");
+ } else {
+ if (pledge("stdio rpath wpath cpath inet unix dns sendfd", NULL) < 0)
+ LOG_FATAL("pledge() failed");
+ }
+ } else {
+ /* No NTS-KE helper(s) will be forked, no need to set 'sendfd' promise */
+ if (geteuid() == 0) {
+ /* Running as root, in addition settime pledge promise needed */
+ if (pledge("stdio rpath wpath cpath inet unix dns settime", NULL) < 0)
+ LOG_FATAL("pledge() failed");
+ } else {
+ if (pledge("stdio rpath wpath cpath inet unix dns", NULL) < 0)
+ LOG_FATAL("pledge() failed");
+ }
+ }
+ } else if (context == SYS_PRIVOPS_HELPER) {
+ /* stdio => allow libc stdio calls
+ settime => allow set/adjust time */
+ if (pledge("stdio settime", NULL) < 0)
+ LOG_FATAL("pledge() failed");
+ } else if (context == SYS_NTSKE_HELPER) {
+ /* stdio => allow libc stdio calls
+ recvfd => allow receiving fd from main process. In run_helper()
+ -> handle_helper_request() */
+ if (pledge("stdio recvfd", NULL) < 0)
+ LOG_FATAL("pledge() failed");
+ }
+
+ LOG(context == SYS_MAIN_PROCESS ? LOGS_INFO : LOGS_DEBUG, "Loaded pledge filter");
+}
+
+#endif
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Shaun Ren 2021
+ * Copyright (C) Thomas Kupper 2026
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for OpenBSD driver
+ */
+
+#ifndef GOT_SYS_OPENBSD_H
+#define GOT_SYS_OPENBSD_H
+
+#include "sys.h"
+
+void SYS_OpenBSD_Initialise(void);
+
+void SYS_OpenBSD_Finalise(void);
+
+void SYS_OpenBSD_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context, int clock_control);
+
+void SYS_OpenBSD_EnableSystemCallFilter(int level, SYS_ProcessContext context);
+
+#endif
test_start "system call filter in non-destructive tests"
-for level in 1 2 -1 -2; do
+os_name=$(uname -s)
+case "$os_name" in
+ OpenBSD)
+ supported_levels=1
+ ;;
+ Linux)
+ supported_levels="1 2 -1 -2"
+ ;;
+ *)
+ test_message 1 1 "unsupported OS $os_name"
+ test_fail
+ ;;
+esac
+
+for level in $supported_levels; do
test_message 1 1 "level $level:"
for test in 0[0-8][0-9]-*[^_]; do
test_message 2 0 "$test"
test_start "system call filter in destructive tests"
-for level in 1 2 -1 -2; do
+os_name="$(uname -s)"
+case $os_name in
+ OpenBSD)
+ supported_levels=1
+ ;;
+ Linux)
+ supported_levels="1 2 -1 -2"
+ ;;
+ *)
+ test_message 1 1 "unsupported OS $os_name"
+ test_fail
+ ;;
+esac
+
+for level in $supported_levels; do
test_message 1 1 "level $level:"
for test in 1[0-8][0-9]-*[^_]; do
test_message 2 0 "$test"