summaryrefslogtreecommitdiff
path: root/sys_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys_linux.c')
-rw-r--r--sys_linux.c361
1 files changed, 256 insertions, 105 deletions
diff --git a/sys_linux.c b/sys_linux.c
index e5ca9a6..092fd84 100644
--- a/sys_linux.c
+++ b/sys_linux.c
@@ -4,7 +4,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) John G. Hasler 2009
- * Copyright (C) Miroslav Lichvar 2009-2012, 2014
+ * Copyright (C) Miroslav Lichvar 2009-2012, 2014-2015
*
* 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
@@ -35,31 +35,48 @@
#if defined(HAVE_SCHED_SETSCHEDULER)
# include <sched.h>
-int SchedPriority = 0;
#endif
#if defined(HAVE_MLOCKALL)
# include <sys/mman.h>
#include <sys/resource.h>
-int LockAll = 0;
#endif
#ifdef FEAT_PRIVDROP
-#include <sys/types.h>
-#include <pwd.h>
#include <sys/prctl.h>
#include <sys/capability.h>
#include <grp.h>
#endif
-#include "sys_generic.h"
+#ifdef FEAT_SCFILTER
+#include <sys/prctl.h>
+#include <seccomp.h>
+#ifdef FEAT_PHC
+#include <linux/ptp_clock.h>
+#endif
+#ifdef FEAT_PPS
+#include <linux/pps.h>
+#endif
+#ifdef FEAT_RTC
+#include <linux/rtc.h>
+#endif
+#endif
+
#include "sys_linux.h"
+#include "sys_timex.h"
#include "conf.h"
#include "logging.h"
-#include "wrap_adjtimex.h"
-/* The threshold for adjtimex maxerror when the kernel sets the UNSYNC flag */
-#define UNSYNC_MAXERROR 16.0
+/* Frequency scale to convert from ppm to the timex freq */
+#define FREQ_SCALE (double)(1 << 16)
+
+/* Definitions used if missed in the system headers */
+#ifndef ADJ_SETOFFSET
+#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */
+#endif
+#ifndef ADJ_NANO
+#define ADJ_NANO 0x2000 /* select nanosecond resolution */
+#endif
/* This is the uncompensated system tick value */
static int nominal_tick;
@@ -103,11 +120,19 @@ our_round(double x)
static int
apply_step_offset(double offset)
{
- if (TMX_ApplyStepOffset(-offset) < 0) {
- DEBUG_LOG(LOGF_SysLinux, "adjtimex() failed");
- return 0;
+ struct timex txc;
+
+ txc.modes = ADJ_SETOFFSET | ADJ_NANO;
+ txc.time.tv_sec = -offset;
+ txc.time.tv_usec = 1.0e9 * (-offset - txc.time.tv_sec);
+ if (txc.time.tv_usec < 0) {
+ txc.time.tv_sec--;
+ txc.time.tv_usec += 1000000000;
}
+ if (SYS_Timex_Adjust(&txc, 1) < 0)
+ return 0;
+
return 1;
}
@@ -121,6 +146,7 @@ apply_step_offset(double offset)
static double
set_frequency(double freq_ppm)
{
+ struct timex txc;
long required_tick;
double required_freq;
int required_delta_tick;
@@ -144,14 +170,15 @@ set_frequency(double freq_ppm)
required_freq = -(freq_ppm - dhz * required_delta_tick);
required_tick = nominal_tick - required_delta_tick;
- if (TMX_SetFrequency(&required_freq, required_tick) < 0) {
- LOG_FATAL(LOGF_SysLinux, "adjtimex failed for set_frequency, freq_ppm=%10.4e required_freq=%10.4e required_tick=%ld",
- freq_ppm, required_freq, required_tick);
- }
+ txc.modes = ADJ_TICK | ADJ_FREQUENCY;
+ txc.freq = required_freq * FREQ_SCALE;
+ txc.tick = required_tick;
+
+ SYS_Timex_Adjust(&txc, 0);
current_delta_tick = required_delta_tick;
- return dhz * current_delta_tick - required_freq;
+ return dhz * current_delta_tick - txc.freq / FREQ_SCALE;
}
/* ================================================== */
@@ -160,61 +187,15 @@ set_frequency(double freq_ppm)
static double
read_frequency(void)
{
- long tick;
- double freq;
-
- if (TMX_GetFrequency(&freq, &tick) < 0) {
- LOG_FATAL(LOGF_SysLinux, "adjtimex() failed");
- }
-
- current_delta_tick = nominal_tick - tick;
-
- return dhz * current_delta_tick - freq;
-}
-
-/* ================================================== */
-
-static void
-set_leap(int leap)
-{
- int current_leap;
-
- if (TMX_GetLeap(&current_leap) < 0) {
- LOG_FATAL(LOGF_SysLinux, "adjtimex() failed in set_leap");
- }
-
- if (current_leap == leap)
- return;
+ struct timex txc;
- if (TMX_SetLeap(leap) < 0) {
- LOG_FATAL(LOGF_SysLinux, "adjtimex() failed in set_leap");
- }
-
- LOG(LOGS_INFO, LOGF_SysLinux, "System clock status set to %s leap second",
- leap ? (leap > 0 ? "insert" : "delete") : "not insert/delete");
-}
+ txc.modes = 0;
-/* ================================================== */
-
-static void
-set_sync_status(int synchronised, double est_error, double max_error)
-{
- if (synchronised) {
- if (est_error > UNSYNC_MAXERROR)
- est_error = UNSYNC_MAXERROR;
- if (max_error >= UNSYNC_MAXERROR) {
- max_error = UNSYNC_MAXERROR;
- synchronised = 0;
- }
- } else {
- est_error = max_error = UNSYNC_MAXERROR;
- }
+ SYS_Timex_Adjust(&txc, 0);
- /* Clear the UNSYNC flag only if rtcsync is enabled */
- if (!CNF_GetRtcSync())
- synchronised = 0;
+ current_delta_tick = nominal_tick - txc.tick;
- TMX_SetSync(synchronised, est_error, max_error);
+ return dhz * current_delta_tick - txc.freq / FREQ_SCALE;
}
/* ================================================== */
@@ -225,10 +206,16 @@ set_sync_status(int synchronised, double est_error, double max_error)
* a +/- 10% movement of tick away from the nominal value 1e6/USER_HZ. */
static int
-guess_hz(int tick)
+guess_hz(void)
{
- int i, tick_lo, tick_hi, ihz;
+ struct timex txc;
+ int i, tick, tick_lo, tick_hi, ihz;
double tick_nominal;
+
+ txc.modes = 0;
+ SYS_Timex_Adjust(&txc, 0);
+ tick = txc.tick;
+
/* Pick off the hz=100 case first */
if (tick >= 9000 && tick <= 11000) {
return 100;
@@ -246,6 +233,8 @@ guess_hz(int tick)
}
/* oh dear. doomed. */
+ LOG_FATAL(LOGF_SysLinux, "Can't determine hz from tick %d", tick);
+
return 0;
}
@@ -287,21 +276,12 @@ static void
get_version_specific_details(void)
{
int major, minor, patch;
- long tick;
- double freq;
struct utsname uts;
hz = get_hz();
- if (!hz) {
- if (TMX_GetFrequency(&freq, &tick) < 0)
- LOG_FATAL(LOGF_SysLinux, "adjtimex() failed");
-
- hz = guess_hz(tick);
-
- if (!hz)
- LOG_FATAL(LOGF_SysLinux, "Can't determine hz from tick %ld", tick);
- }
+ if (!hz)
+ hz = guess_hz();
dhz = (double) hz;
nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */
@@ -345,6 +325,48 @@ get_version_specific_details(void)
}
/* ================================================== */
+
+static void
+reset_adjtime_offset(void)
+{
+ struct timex txc;
+
+ /* Reset adjtime() offset */
+ txc.modes = ADJ_OFFSET_SINGLESHOT;
+ txc.offset = 0;
+
+ SYS_Timex_Adjust(&txc, 0);
+}
+
+/* ================================================== */
+
+static int
+test_step_offset(void)
+{
+ struct timex txc;
+
+ /* Zero maxerror and check it's reset to a maximum after ADJ_SETOFFSET.
+ This seems to be the only way how to verify that the kernel really
+ supports the ADJ_SETOFFSET mode as it doesn't return an error on unknown
+ mode. */
+
+ txc.modes = MOD_MAXERROR;
+ txc.maxerror = 0;
+
+ if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror != 0)
+ return 0;
+
+ txc.modes = ADJ_SETOFFSET | ADJ_NANO;
+ txc.time.tv_sec = 0;
+ txc.time.tv_usec = 0;
+
+ if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror < 100000)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
/* Initialisation code for this module */
void
@@ -352,20 +374,18 @@ SYS_Linux_Initialise(void)
{
get_version_specific_details();
- if (TMX_ResetOffset() < 0) {
- LOG_FATAL(LOGF_SysLinux, "adjtimex() failed");
- }
+ reset_adjtime_offset();
- if (have_setoffset && TMX_TestStepOffset() < 0) {
+ if (have_setoffset && !test_step_offset()) {
LOG(LOGS_INFO, LOGF_SysLinux, "adjtimex() doesn't support ADJ_SETOFFSET");
have_setoffset = 0;
}
- SYS_Generic_CompleteFreqDriver(1.0e6 * max_tick_bias / nominal_tick,
- 1.0 / tick_update_hz,
- read_frequency, set_frequency,
- have_setoffset ? apply_step_offset : NULL,
- set_leap, set_sync_status);
+ SYS_Timex_InitialiseWithFunctions(1.0e6 * max_tick_bias / nominal_tick,
+ 1.0 / tick_update_hz,
+ read_frequency, set_frequency,
+ have_setoffset ? apply_step_offset : NULL,
+ 0.0, 0.0, NULL, NULL);
}
/* ================================================== */
@@ -374,25 +394,17 @@ SYS_Linux_Initialise(void)
void
SYS_Linux_Finalise(void)
{
- SYS_Generic_Finalise();
+ SYS_Timex_Finalise();
}
/* ================================================== */
#ifdef FEAT_PRIVDROP
void
-SYS_Linux_DropRoot(char *user)
+SYS_Linux_DropRoot(uid_t uid, gid_t gid)
{
- struct passwd *pw;
cap_t cap;
- if (user == NULL)
- return;
-
- if ((pw = getpwnam(user)) == NULL) {
- LOG_FATAL(LOGF_SysLinux, "getpwnam(%s) failed", user);
- }
-
if (prctl(PR_SET_KEEPCAPS, 1)) {
LOG_FATAL(LOGF_SysLinux, "prctl() failed");
}
@@ -401,12 +413,12 @@ SYS_Linux_DropRoot(char *user)
LOG_FATAL(LOGF_SysLinux, "setgroups() failed");
}
- if (setgid(pw->pw_gid)) {
- LOG_FATAL(LOGF_SysLinux, "setgid(%d) failed", pw->pw_gid);
+ if (setgid(gid)) {
+ LOG_FATAL(LOGF_SysLinux, "setgid(%d) failed", gid);
}
- if (setuid(pw->pw_uid)) {
- LOG_FATAL(LOGF_SysLinux, "setuid(%d) failed", pw->pw_uid);
+ if (setuid(uid)) {
+ LOG_FATAL(LOGF_SysLinux, "setuid(%d) failed", uid);
}
if ((cap = cap_from_text("cap_net_bind_service,cap_sys_time=ep")) == NULL) {
@@ -419,7 +431,146 @@ SYS_Linux_DropRoot(char *user)
cap_free(cap);
- DEBUG_LOG(LOGF_SysLinux, "Privileges dropped to user %s", user);
+ DEBUG_LOG(LOGF_SysLinux, "Root dropped to uid %d gid %d", uid, gid);
+}
+#endif
+
+/* ================================================== */
+
+#ifdef FEAT_SCFILTER
+static
+void check_seccomp_applicability(void)
+{
+ int mail_enabled;
+ double mail_threshold;
+ char *mail_user;
+
+ CNF_GetMailOnChange(&mail_enabled, &mail_threshold, &mail_user);
+ if (mail_enabled)
+ LOG_FATAL(LOGF_SysLinux, "mailonchange directive cannot be used with -F enabled");
+}
+
+/* ================================================== */
+
+void
+SYS_Linux_EnableSystemCallFilter(int level)
+{
+ const int syscalls[] = {
+ /* Clock */
+ SCMP_SYS(adjtimex), SCMP_SYS(gettimeofday), SCMP_SYS(settimeofday),
+ SCMP_SYS(time),
+ /* Process */
+ SCMP_SYS(clone), SCMP_SYS(exit), SCMP_SYS(exit_group),
+ SCMP_SYS(rt_sigreturn), SCMP_SYS(sigreturn),
+ /* Memory */
+ SCMP_SYS(brk), SCMP_SYS(madvise), SCMP_SYS(mmap), SCMP_SYS(mmap2),
+ SCMP_SYS(mprotect), SCMP_SYS(munmap), SCMP_SYS(shmdt),
+ /* Filesystem */
+ SCMP_SYS(chmod), SCMP_SYS(chown), SCMP_SYS(chown32), SCMP_SYS(fstat),
+ SCMP_SYS(fstat64), SCMP_SYS(lseek), SCMP_SYS(rename), SCMP_SYS(stat),
+ SCMP_SYS(stat64), SCMP_SYS(unlink),
+ /* Socket */
+ SCMP_SYS(bind), SCMP_SYS(connect), SCMP_SYS(getsockname),
+ SCMP_SYS(recvfrom), SCMP_SYS(recvmsg), SCMP_SYS(sendmmsg),
+ SCMP_SYS(sendmsg), SCMP_SYS(sendto),
+ /* TODO: check socketcall arguments */
+ SCMP_SYS(socketcall),
+ /* General I/O */
+ SCMP_SYS(_newselect), SCMP_SYS(close), SCMP_SYS(open), SCMP_SYS(pipe),
+ SCMP_SYS(poll), SCMP_SYS(read), SCMP_SYS(futex), SCMP_SYS(select),
+ SCMP_SYS(set_robust_list), SCMP_SYS(write),
+ /* Miscellaneous */
+ SCMP_SYS(uname),
+ };
+
+ const int socket_domains[] = {
+ AF_NETLINK, AF_UNIX, AF_INET,
+#ifdef FEAT_IPV6
+ AF_INET6,
+#endif
+ };
+
+ const static int socket_options[][2] = {
+ { SOL_IP, IP_PKTINFO }, { SOL_IP, IP_FREEBIND },
+#ifdef FEAT_IPV6
+ { SOL_IPV6, IPV6_V6ONLY }, { SOL_IPV6, IPV6_RECVPKTINFO },
+#endif
+ { SOL_SOCKET, SO_BROADCAST }, { SOL_SOCKET, SO_REUSEADDR },
+ { SOL_SOCKET, SO_TIMESTAMP },
+ };
+
+ const static int fcntls[] = { F_GETFD, F_SETFD };
+
+ const static unsigned long ioctls[] = {
+ FIONREAD,
+#ifdef FEAT_PPS
+ PTP_SYS_OFFSET,
+#endif
+#ifdef FEAT_PPS
+ PPS_FETCH,
+#endif
+#ifdef FEAT_RTC
+ RTC_RD_TIME, RTC_SET_TIME, RTC_UIE_ON, RTC_UIE_OFF,
+#endif
+ };
+
+ scmp_filter_ctx *ctx;
+ int i;
+
+ /* Check if the chronyd configuration is supported */
+ check_seccomp_applicability();
+
+ ctx = seccomp_init(level > 0 ? SCMP_ACT_KILL : SCMP_ACT_TRAP);
+ if (ctx == NULL)
+ LOG_FATAL(LOGF_SysLinux, "Failed to initialize seccomp");
+
+ /* Add system calls that are always allowed */
+ for (i = 0; i < (sizeof (syscalls) / sizeof (*syscalls)); i++) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls[i], 0) < 0)
+ goto add_failed;
+ }
+
+ /* Allow sockets to be created only in selected domains */
+ for (i = 0; i < sizeof (socket_domains) / sizeof (*socket_domains); i++) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
+ SCMP_A0(SCMP_CMP_EQ, socket_domains[i])) < 0)
+ goto add_failed;
+ }
+
+ /* Allow setting only selected sockets options */
+ for (i = 0; i < sizeof (socket_options) / sizeof (*socket_options); i++) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 3,
+ SCMP_A1(SCMP_CMP_EQ, socket_options[i][0]),
+ SCMP_A2(SCMP_CMP_EQ, socket_options[i][1]),
+ SCMP_A4(SCMP_CMP_LE, sizeof (int))) < 0)
+ goto add_failed;
+ }
+
+ /* Allow only selected fcntl calls */
+ for (i = 0; i < sizeof (fcntls) / sizeof (*fcntls); i++) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1,
+ SCMP_A1(SCMP_CMP_EQ, fcntls[i])) < 0 ||
+ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), 1,
+ SCMP_A1(SCMP_CMP_EQ, fcntls[i])) < 0)
+ goto add_failed;
+ }
+
+ /* Allow only selected ioctls */
+ for (i = 0; i < sizeof (ioctls) / sizeof (*ioctls); i++) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
+ SCMP_A1(SCMP_CMP_EQ, ioctls[i])) < 0)
+ goto add_failed;
+ }
+
+ if (seccomp_load(ctx) < 0)
+ LOG_FATAL(LOGF_SysLinux, "Failed to load seccomp rules");
+
+ LOG(LOGS_INFO, LOGF_SysLinux, "Loaded seccomp filter");
+ seccomp_release(ctx);
+ return;
+
+add_failed:
+ LOG_FATAL(LOGF_SysLinux, "Failed to add seccomp rules");
}
#endif