sys_linux: add support for seccomp filters
The Linux secure computing (seccomp) facility allows a process to install a filter in the kernel that will allow only specific system calls to be made. The process is killed when trying to make other system calls. This is useful to reduce the kernel attack surface and possibly prevent kernel exploits when the process is compromised. Use the libseccomp library to add rules and load the filter into the kernel. Keep a list of system calls that are always allowed after chronyd is initialized. Restrict arguments that may be passed to the socket(), setsockopt(), fcntl(), and ioctl() system calls. Arguments to socketcall(), which is used on some architectures as a multiplexer instead of separate socket system calls, are not restricted for now. The mailonchange directive is not allowed as it calls sendmail. Calls made by the libraries that chronyd is using have to be covered too. It's difficult to determine which system calls they need as it may change after an upgrade and it may depend on their configuration (e.g. resolver in libc). There are also differences between architectures. It can all break very easily and is therefore disabled by default. It can be enabled with the new -F option. This is based on a patch from Andrew Griffiths <agriffit@redhat.com>.
This commit is contained in:
parent
ea2858b323
commit
434faeecb8
8 changed files with 203 additions and 2 deletions
|
@ -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
|
@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
|
non-root user only when @code{chronyd} is compiled with support for Linux
|
||||||
capabilities (libcap).
|
capabilities (libcap).
|
||||||
|
@item -F <level>
|
||||||
|
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
|
@item -q
|
||||||
When run in this mode, @code{chronyd} will set the system clock once
|
When run in this mode, @code{chronyd} will set the system clock once
|
||||||
and exit. It will not detach from the terminal.
|
and exit. It will not detach from the terminal.
|
||||||
|
|
|
@ -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
|
only when \fBchronyd\fR is compiled with support for Linux capabilities
|
||||||
(libcap).
|
(libcap).
|
||||||
.TP
|
.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
|
.B \-q
|
||||||
When run in this mode, chronyd will set the system clock once
|
When run in this mode, chronyd will set the system clock once
|
||||||
and exit. It will not detach from the terminal.
|
and exit. It will not detach from the terminal.
|
||||||
|
|
17
configure
vendored
17
configure
vendored
|
@ -115,6 +115,7 @@ For better control, use the options below.
|
||||||
--disable-rtc Don't include RTC even on Linux
|
--disable-rtc Don't include RTC even on Linux
|
||||||
--disable-privdrop Disable support for dropping root privileges
|
--disable-privdrop Disable support for dropping root privileges
|
||||||
--without-libcap Don't use libcap even if it is available
|
--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-asyncdns Disable asynchronous name resolving
|
||||||
--disable-forcednsretry Don't retry on permanent DNS error
|
--disable-forcednsretry Don't retry on permanent DNS error
|
||||||
--with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds
|
--with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds
|
||||||
|
@ -217,6 +218,8 @@ try_rtc=0
|
||||||
feat_droproot=1
|
feat_droproot=1
|
||||||
try_libcap=-1
|
try_libcap=-1
|
||||||
try_clockctl=0
|
try_clockctl=0
|
||||||
|
feat_scfilter=1
|
||||||
|
try_seccomp=-1
|
||||||
readline_lib=""
|
readline_lib=""
|
||||||
readline_inc=""
|
readline_inc=""
|
||||||
ncurses_lib=""
|
ncurses_lib=""
|
||||||
|
@ -322,6 +325,9 @@ do
|
||||||
--disable-asyncdns)
|
--disable-asyncdns)
|
||||||
feat_asyncdns=0
|
feat_asyncdns=0
|
||||||
;;
|
;;
|
||||||
|
--without-seccomp)
|
||||||
|
try_seccomp=0
|
||||||
|
;;
|
||||||
--disable-forcednsretry)
|
--disable-forcednsretry)
|
||||||
feat_forcednsretry=0
|
feat_forcednsretry=0
|
||||||
;;
|
;;
|
||||||
|
@ -387,6 +393,7 @@ case $SYSTEM in
|
||||||
EXTRA_OBJECTS="sys_generic.o sys_linux.o wrap_adjtimex.o"
|
EXTRA_OBJECTS="sys_generic.o sys_linux.o wrap_adjtimex.o"
|
||||||
[ $try_libcap != "0" ] && try_libcap=1
|
[ $try_libcap != "0" ] && try_libcap=1
|
||||||
try_rtc=1
|
try_rtc=1
|
||||||
|
[ $try_seccomp != "0" ] && try_seccomp=1
|
||||||
try_setsched=1
|
try_setsched=1
|
||||||
try_lockmem=1
|
try_lockmem=1
|
||||||
try_phc=1
|
try_phc=1
|
||||||
|
@ -602,6 +609,14 @@ then
|
||||||
add_def FEAT_PRIVDROP
|
add_def FEAT_PRIVDROP
|
||||||
fi
|
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" ] && \
|
if [ $feat_rtc = "1" ] && [ $try_rtc = "1" ] && \
|
||||||
test_code '<linux/rtc.h>' 'sys/ioctl.h linux/rtc.h' '' '' \
|
test_code '<linux/rtc.h>' 'sys/ioctl.h linux/rtc.h' '' '' \
|
||||||
'ioctl(1, RTC_UIE_ON&RTC_UIE_OFF&RTC_RD_TIME&RTC_SET_TIME, 0&RTC_UF);'
|
'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`"
|
common_features="`get_features ASYNCDNS IPV6 SECHASH DEBUG`"
|
||||||
chronyc_features="`get_features READLINE`"
|
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 CHRONYC_FEATURES "\"$chronyc_features $common_features\""
|
||||||
add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\""
|
add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\""
|
||||||
echo "Features : $chronyd_features $chronyc_features $common_features"
|
echo "Features : $chronyd_features $chronyc_features $common_features"
|
||||||
|
|
9
main.c
9
main.c
|
@ -354,7 +354,7 @@ int main
|
||||||
int debug = 0, nofork = 0, address_family = IPADDR_UNSPEC;
|
int debug = 0, nofork = 0, address_family = IPADDR_UNSPEC;
|
||||||
int do_init_rtc = 0, restarted = 0;
|
int do_init_rtc = 0, restarted = 0;
|
||||||
int other_pid;
|
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 system_log = 1;
|
||||||
int config_args = 0;
|
int config_args = 0;
|
||||||
|
|
||||||
|
@ -384,6 +384,10 @@ int main
|
||||||
} else {
|
} else {
|
||||||
user = *argv;
|
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)) {
|
} else if (!strcmp("-s", *argv)) {
|
||||||
do_init_rtc = 1;
|
do_init_rtc = 1;
|
||||||
} else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) {
|
} else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) {
|
||||||
|
@ -521,6 +525,9 @@ int main
|
||||||
|
|
||||||
CAM_OpenUnixSocket();
|
CAM_OpenUnixSocket();
|
||||||
|
|
||||||
|
if (scfilter_level)
|
||||||
|
SYS_EnableSystemCallFilter(scfilter_level);
|
||||||
|
|
||||||
if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) {
|
if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) {
|
||||||
ref_mode = REF_ModeInitStepSlew;
|
ref_mode = REF_ModeInitStepSlew;
|
||||||
}
|
}
|
||||||
|
|
11
sys.c
11
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)
|
void SYS_SetScheduler(int SchedPriority)
|
||||||
{
|
{
|
||||||
#if defined(LINUX) && defined(HAVE_SCHED_SETSCHEDULER)
|
#if defined(LINUX) && defined(HAVE_SCHED_SETSCHEDULER)
|
||||||
|
|
4
sys.h
4
sys.h
|
@ -38,6 +38,10 @@ extern void SYS_Finalise(void);
|
||||||
/* Drop root privileges to the specified user and group */
|
/* Drop root privileges to the specified user and group */
|
||||||
extern void SYS_DropRoot(uid_t uid, gid_t gid);
|
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_SetScheduler(int SchedPriority);
|
||||||
extern void SYS_LockMemory(void);
|
extern void SYS_LockMemory(void);
|
||||||
|
|
||||||
|
|
151
sys_linux.c
151
sys_linux.c
|
@ -48,6 +48,20 @@
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#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_generic.h"
|
#include "sys_generic.h"
|
||||||
#include "sys_linux.h"
|
#include "sys_linux.h"
|
||||||
#include "conf.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)
|
#if defined(HAVE_SCHED_SETSCHEDULER)
|
||||||
/* Install SCHED_FIFO real-time scheduler with specified priority */
|
/* Install SCHED_FIFO real-time scheduler with specified priority */
|
||||||
void SYS_Linux_SetScheduler(int SchedPriority)
|
void SYS_Linux_SetScheduler(int SchedPriority)
|
||||||
|
|
|
@ -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_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_MemLockAll(int LockAll);
|
||||||
|
|
||||||
extern void SYS_Linux_SetScheduler(int SchedPriority);
|
extern void SYS_Linux_SetScheduler(int SchedPriority);
|
||||||
|
|
Loading…
Reference in a new issue