diff options
Diffstat (limited to 'sys_linux.c')
-rw-r--r-- | sys_linux.c | 361 |
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(¤t_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 |