diff --git a/chrony.texi.in b/chrony.texi.in index 30b429a..b653e45 100644 --- a/chrony.texi.in +++ b/chrony.texi.in @@ -1029,6 +1029,11 @@ switch after start in order to drop root privileges. It overrides the @code{user} directive (default @code{@DEFAULT_USER@}). It may be set to a non-root user only when @code{chronyd} is compiled with support for Linux capabilities (libcap). +@item -F +This option configures a system call filter when @code{chronyd} is compiled with +support for the Linux secure computing (seccomp) facility. In level 1 the +process is killed when a forbidden system call is made, in level -1 the SYSSIG +signal is thrown instead and in level 0 the filter is disabled (default 0). @item -q When run in this mode, @code{chronyd} will set the system clock once and exit. It will not detach from the terminal. diff --git a/chronyd.8.in b/chronyd.8.in index 85e26bd..fa0cd26 100644 --- a/chronyd.8.in +++ b/chronyd.8.in @@ -111,6 +111,12 @@ directive (default \fB@DEFAULT_USER@\fR). It may be set to a non-root user only when \fBchronyd\fR is compiled with support for Linux capabilities (libcap). .TP +\fB\-F\fR \fIlevel\fR +This option configures a system call filter when \fBchronyd\fR is compiled with +support for the Linux secure computing (seccomp) facility. In level 1 the +process is killed when a forbidden system call is made, in level -1 the SYSSIG +signal is thrown instead and in level 0 the filter is disabled (default 0). +.TP .B \-q When run in this mode, chronyd will set the system clock once and exit. It will not detach from the terminal. diff --git a/configure b/configure index f7a68f5..1f06744 100755 --- a/configure +++ b/configure @@ -115,6 +115,7 @@ For better control, use the options below. --disable-rtc Don't include RTC even on Linux --disable-privdrop Disable support for dropping root privileges --without-libcap Don't use libcap even if it is available + --without-seccomp Don't use seccomp even if it is available --disable-asyncdns Disable asynchronous name resolving --disable-forcednsretry Don't retry on permanent DNS error --with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds @@ -217,6 +218,8 @@ try_rtc=0 feat_droproot=1 try_libcap=-1 try_clockctl=0 +feat_scfilter=1 +try_seccomp=-1 readline_lib="" readline_inc="" ncurses_lib="" @@ -322,6 +325,9 @@ do --disable-asyncdns) feat_asyncdns=0 ;; + --without-seccomp) + try_seccomp=0 + ;; --disable-forcednsretry) feat_forcednsretry=0 ;; @@ -387,6 +393,7 @@ case $SYSTEM in EXTRA_OBJECTS="sys_generic.o sys_linux.o wrap_adjtimex.o" [ $try_libcap != "0" ] && try_libcap=1 try_rtc=1 + [ $try_seccomp != "0" ] && try_seccomp=1 try_setsched=1 try_lockmem=1 try_phc=1 @@ -602,6 +609,14 @@ then add_def FEAT_PRIVDROP fi +if [ $feat_scfilter = "1" ] && [ $try_seccomp = "1" ] && \ + test_code seccomp 'seccomp.h' '' '-lseccomp' \ + 'seccomp_init(SCMP_ACT_KILL);' +then + add_def FEAT_SCFILTER + EXTRA_LIBS="$EXTRA_LIBS -lseccomp" +fi + if [ $feat_rtc = "1" ] && [ $try_rtc = "1" ] && \ test_code '' 'sys/ioctl.h linux/rtc.h' '' '' \ 'ioctl(1, RTC_UIE_ON&RTC_UIE_OFF&RTC_RD_TIME&RTC_SET_TIME, 0&RTC_UF);' @@ -792,7 +807,7 @@ add_def MAIL_PROGRAM "\"$mail_program\"" common_features="`get_features ASYNCDNS IPV6 SECHASH DEBUG`" chronyc_features="`get_features READLINE`" -chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP`" +chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER`" add_def CHRONYC_FEATURES "\"$chronyc_features $common_features\"" add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\"" echo "Features : $chronyd_features $chronyc_features $common_features" diff --git a/main.c b/main.c index 17cdf70..83f0c4a 100644 --- a/main.c +++ b/main.c @@ -354,7 +354,7 @@ int main int debug = 0, nofork = 0, address_family = IPADDR_UNSPEC; int do_init_rtc = 0, restarted = 0; int other_pid; - int lock_memory = 0, sched_priority = 0; + int scfilter_level = 0, lock_memory = 0, sched_priority = 0; int system_log = 1; int config_args = 0; @@ -384,6 +384,10 @@ int main } else { user = *argv; } + } else if (!strcmp("-F", *argv)) { + ++argv, --argc; + if (argc == 0 || sscanf(*argv, "%d", &scfilter_level) != 1) + LOG_FATAL(LOGF_Main, "Bad syscall filter level"); } else if (!strcmp("-s", *argv)) { do_init_rtc = 1; } else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) { @@ -521,6 +525,9 @@ int main CAM_OpenUnixSocket(); + if (scfilter_level) + SYS_EnableSystemCallFilter(scfilter_level); + if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) { ref_mode = REF_ModeInitStepSlew; } diff --git a/sys.c b/sys.c index 94fc217..c5c4250 100644 --- a/sys.c +++ b/sys.c @@ -122,6 +122,17 @@ void SYS_DropRoot(uid_t uid, gid_t gid) /* ================================================== */ +void SYS_EnableSystemCallFilter(int level) +{ +#if defined(LINUX) && defined(FEAT_SCFILTER) + SYS_Linux_EnableSystemCallFilter(level); +#else + LOG_FATAL(LOGF_Sys, "system call filter not supported"); +#endif +} + +/* ================================================== */ + void SYS_SetScheduler(int SchedPriority) { #if defined(LINUX) && defined(HAVE_SCHED_SETSCHEDULER) diff --git a/sys.h b/sys.h index a3f58c0..bd990e2 100644 --- a/sys.h +++ b/sys.h @@ -38,6 +38,10 @@ extern void SYS_Finalise(void); /* Drop root privileges to the specified user and group */ extern void SYS_DropRoot(uid_t uid, gid_t gid); +/* Enable a system call filter to allow only system calls + which chronyd normally needs after initialization */ +extern void SYS_EnableSystemCallFilter(int level); + extern void SYS_SetScheduler(int SchedPriority); extern void SYS_LockMemory(void); diff --git a/sys_linux.c b/sys_linux.c index c315015..f35bc67 100644 --- a/sys_linux.c +++ b/sys_linux.c @@ -48,6 +48,20 @@ #include #endif +#ifdef FEAT_SCFILTER +#include +#include +#ifdef FEAT_PHC +#include +#endif +#ifdef FEAT_PPS +#include +#endif +#ifdef FEAT_RTC +#include +#endif +#endif + #include "sys_generic.h" #include "sys_linux.h" #include "conf.h" @@ -412,6 +426,143 @@ SYS_Linux_DropRoot(uid_t uid, gid_t gid) /* ================================================== */ +#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), + }; + + 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 }, +#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(LOGS_INFO, 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 + +/* ================================================== */ + #if defined(HAVE_SCHED_SETSCHEDULER) /* Install SCHED_FIFO real-time scheduler with specified priority */ void SYS_Linux_SetScheduler(int SchedPriority) diff --git a/sys_linux.h b/sys_linux.h index 6c69082..f2c6d59 100644 --- a/sys_linux.h +++ b/sys_linux.h @@ -33,6 +33,8 @@ extern void SYS_Linux_Finalise(void); extern void SYS_Linux_DropRoot(uid_t uid, gid_t gid); +extern void SYS_Linux_EnableSystemCallFilter(int level); + extern void SYS_Linux_MemLockAll(int LockAll); extern void SYS_Linux_SetScheduler(int SchedPriority);