Files
chrony/sys_openbsd.c
Thomas Kupper 4ddc6b334d sys: add OpenBSD support
Add OpenBSD support, including pledge(2) support by implementing
SYS_EnableSystemCallFilter().

This commit depends on the addition of AdjustFreq() privops and the
addtion of invoking SYS_EnableSystemCallFilter() from PRV_StartHelper().

Only system call filter levels on/off' are supported. Setting level
to 0 disables the filter and setting it to 1 enables it.

Update the documentation to reflect that OpenBSD supports:
- the SCHED_FIFO real-time scheduler (option -P)
- locking chronyd into memory (option -m)
- reload sample history of servers and ref clocks (option -r)
- forking into two process when run as non-root user (option -u)
- maxdrift/maxslewrate of 100000.
2026-02-25 12:35:50 +01:00

282 lines
7.6 KiB
C

/*
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