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:
Miroslav Lichvar
2014-06-16 16:21:25 +02:00
parent ea2858b323
commit 434faeecb8
8 changed files with 203 additions and 2 deletions

View File

@@ -48,6 +48,20 @@
#include <grp.h>
#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_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)