This commit is contained in:
namnamc 2016-01-26 12:12:54 +00:00
commit 2cc3e05351
9 changed files with 933 additions and 0 deletions

View file

@ -1,4 +1,5 @@
/* misc.. */ /* misc.. */
#undef HAVE_SECCOMP
#undef HAVE_IPV6 #undef HAVE_IPV6
#undef HAVE_SOCKS_H #undef HAVE_SOCKS_H
#undef HAVE_STATIC_PERL #undef HAVE_STATIC_PERL

View file

@ -140,6 +140,15 @@ AC_ARG_WITH(perl,
fi, fi,
want_perl=static) want_perl=static)
AC_ARG_ENABLE(seccomp,
[ --disable-seccomp, Disable seccomp support],
if test x$enableval = xno; then
want_seccomp=yes
else
want_seccomp=no
fi,
want_seccomp=yes)
AC_ARG_ENABLE(ipv6, AC_ARG_ENABLE(ipv6,
[ --disable-ipv6 Disable IPv6 support], [ --disable-ipv6 Disable IPv6 support],
if test x$enableval = xno; then if test x$enableval = xno; then
@ -577,6 +586,30 @@ COMMON_LIBS="$FE_COMMON_LIBS $COMMON_NOUI_LIBS"
AC_SUBST(COMMON_NOUI_LIBS) AC_SUBST(COMMON_NOUI_LIBS)
AC_SUBST(COMMON_LIBS) AC_SUBST(COMMON_LIBS)
dnl **
dnl ** Seccomp support
dnl **
have_seccomp=no
if test "x$want_seccomp" = "xyes"; then
have_caps=no
AC_CHECK_LIB(cap, cap_init,
[
have_caps=yes
], [], [])
AC_CHECK_LIB(seccomp, seccomp_init,
[
have_seccomp=yes
AC_DEFINE(HAVE_SECCOMP)
LIBS="$LIBS -lseccomp -lcap"
], [], [-lcap])
if test x$have_cap = xno; then
AC_ERROR([Capability support is required to build Irssi with Seccomp support enabled.])
fi
fi
dnl ** dnl **
dnl ** IPv6 support dnl ** IPv6 support
dnl ** dnl **
@ -742,6 +775,7 @@ echo "Install prefix ................... : $prefix"
echo echo
echo "Building with seccomp support .... : $have_seccomp"
echo "Building with IPv6 support ....... : $have_ipv6" echo "Building with IPv6 support ....... : $have_ipv6"
echo "Building with SSL support ........ : $have_openssl" echo "Building with SSL support ........ : $have_openssl"
if test "x$have_openssl" = "xno" -a "x$enable_ssl" = "xyes"; then if test "x$have_openssl" = "xno" -a "x$enable_ssl" = "xyes"; then

View file

@ -37,6 +37,7 @@ libcore_a_SOURCES = \
queries.c \ queries.c \
rawlog.c \ rawlog.c \
recode.c \ recode.c \
sandbox.c \
servers.c \ servers.c \
servers-reconnect.c \ servers-reconnect.c \
servers-setup.c \ servers-setup.c \
@ -86,6 +87,7 @@ pkginc_core_HEADERS = \
queries.h \ queries.h \
rawlog.h \ rawlog.h \
recode.h \ recode.h \
sandbox.h \
servers.h \ servers.h \
servers-reconnect.h \ servers-reconnect.h \
servers-setup.h \ servers-setup.h \

View file

@ -170,4 +170,6 @@ void commands_remove_module(const char *module);
void commands_init(void); void commands_init(void);
void commands_deinit(void); void commands_deinit(void);
extern int sandbox;
#endif #endif

859
src/core/sandbox.c Normal file
View file

@ -0,0 +1,859 @@
/*
sandbox.c : irssi
Copyright (C) 2015 Namsun Ch'o
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <errno.h>
#include <linux/futex.h>
#include <linux/in.h>
#include <linux/prctl.h>
#include <linux/sched.h>
#include <seccomp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/termios.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "sandbox.h"
#define IRSSI_NEEDS_RDTSC
#undef IRSSI_NEEDS_CAPS
/*
* TODO:
* - implement user, pid, network, and mount namespaces
* - ensure that hardcoded file descriptors work
* - create automated test suite for seccomp (and/or static analysis?)
* - keep private keys in a separate process, (e.g https://github.com/AGWA/titus)?
* - add support tame() on OpenBSD, and Capsicum on FreeBSD
*/
struct _cap_struct {
struct __user_cap_header_struct head;
union {
struct __user_cap_data_struct set;
uint32_t flag[3];
} u[2];
};
void create_namespaces(void)
{
/* TODO */
}
void drop_privileges(void)
{
int tsc_state;
size_t i;
uint32_t *raw_data;
cap_t cap;
cap_flag_value_t value;
/* running as root largely defeats the purpose of using a sandbox */
if (geteuid() == 0 || getegid() == 0) {
fprintf(stderr, "Cannot run irssi as root when the sandbox is enabled\n");
exit(1);
}
/* disable the rdtsc so it cannot be used for side-channel attacks */
#ifndef IRSSI_NEEDS_RDTSC
if (prctl(PR_GET_TSC, &tsc_state, 0, 0, 0) < 0) {
fprintf(stderr, "Could not get the TSC state\n");
exit(1);
}
if (tsc_state == PR_TSC_ENABLE) {
prctl(PR_SET_TSC, PR_TSC_SIGSEGV, 0, 0, 0);
prctl(PR_GET_TSC, &tsc_state, 0, 0, 0);
if (tsc_state != PR_TSC_SIGSEGV) {
fprintf(stderr, "Could not disable the TSC\n");
exit(1);
}
}
#endif
/* drop any dangerous capabilities the process is running with
* https://forums.grsecurity.net/viewtopic.php?f=7&t=2522 */
#ifndef IRSSI_NEEDS_CAPS
cap = cap_get_proc();
if (cap == NULL) {
raw_data = malloc(sizeof(uint32_t) + sizeof(*cap));
*raw_data = 0xCA90D0; /* CAP_T_MAGIC */
cap = (cap_t)(raw_data + 1);
memset(cap, 0, sizeof(*cap));
cap->header.version = 0x20080522; /* capability version 3 */
if (capget(&cap->head, &cap->u[0].set) < 0) {
cap_free(cap);
fprintf(stderr, "Could not initialize capabilities (capget failed)\n");
exit(1);
}
}
if (cap_clear(cap) < 0) {
fprintf(stderr, "Could not clear capabilities (cap_clear failed)\n");
cap_free(cap);
exit(1);
}
for (i = 0; i < 64; i++) {
cap_get_flag(cap, i, CAP_EFFECTIVE, &value);
if (value == CAP_SET) {
fprintf(stderr, "Cannot run irssi with caps set (cap %u)\n", (unsigned)i);
cap_free(cap);
exit(1);
}
}
cap_free(cap);
#endif
}
void enforce_resource_limits(void)
{
size_t i;
struct rlimit rlim;
struct rlimit_struct_t {
int resource;
int limit;
};
const struct rlimit_struct_t rlimit_struct[9] = {
{ RLIMIT_AS, 268435456 }, /* 256 MiB */
{ RLIMIT_FSIZE, 1073741824 }, /* 1 GiB */
{ RLIMIT_LOCKS, 0 },
{ RLIMIT_MEMLOCK, 0 },
{ RLIMIT_MSGQUEUE, 0 },
{ RLIMIT_NOFILE, 4096 },
{ RLIMIT_NPROC, 70 }, /* why is this so large? */
{ RLIMIT_SIGPENDING, 0 },
{ RLIMIT_STACK, 32768 } /* 32 KiB */
};
for (i = 0; i < 9; i++) {
rlim.rlim_cur = rlimit_struct[i].limit;
rlim.rlim_max = rlimit_struct[i].limit;
if (setrlimit(rlimit_struct[i].resource, &rlim) < 0 && errno != EPERM) {
fprintf(stderr, "Could not set resource limits. Errno %d\n", errno);
exit(1);
}
}
}
void enable_seccomp_sandbox(void)
{
scmp_filter_ctx ctx;
size_t i;
int rc;
/* initialize the libseccomp context */
ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
fprintf(stderr, "Could not initialize the sandbox (seccomp_init failed)\n");
exit(1);
}
/* syscalls without argument filtering */
const int noarg_whitelist[19] = {
SCMP_SYS(brk),
SCMP_SYS(chdir),
SCMP_SYS(close),
SCMP_SYS(exit_group),
SCMP_SYS(fstat),
SCMP_SYS(getegid),
SCMP_SYS(geteuid),
SCMP_SYS(getgid),
SCMP_SYS(getpid),
SCMP_SYS(getuid),
SCMP_SYS(lseek),
SCMP_SYS(munmap),
SCMP_SYS(pipe),
SCMP_SYS(rt_sigreturn),
SCMP_SYS(set_tid_address),
SCMP_SYS(stat),
SCMP_SYS(umask),
SCMP_SYS(uname)
};
for (i = 0; i < 19; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, noarg_whitelist[i], 0);
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, noarg_whitelist[i], 100);
if (rc < 0)
goto fail;
}
#ifdef __i386__
/* if we are on a 32 bit architecture, we need to use socketcall */
const int socketcall_calls[7] = {
SYS_SOCKET,
SYS_CONNECT,
SYS_GETPEERNAME,
SYS_GETSOCKOPT,
SYS_SETSOCKOPT,
SYS_SENDTO,
SYS_RECVFROM
};
for (i = 0; i < 7; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketcall), 1,
SCMP_A0(SCMP_CMP_EQ, socketcall_calls[i]));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(socketcall), 50);
if (rc < 0)
goto fail;
#endif
/* clone */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 3,
SCMP_A0(SCMP_CMP_EQ, CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD),
SCMP_A1(SCMP_CMP_EQ, 0),
SCMP_A2(SCMP_CMP_EQ, 0));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 1,
SCMP_A0(SCMP_CMP_EQ, CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|
CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|
CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(clone), 50);
if (rc < 0)
goto fail;
/* mkdir */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mkdir), 1,
SCMP_A1(SCMP_CMP_EQ, 0700)); /* src/core/settings.c:828 */
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(mkdir), 50);
if (rc < 0)
goto fail;
#ifndef __i386__
/* setsockopt */
struct setsockopt_struct_t {
int minsockfd;
int level;
int optname;
socklen_t optlen;
};
const struct setsockopt_struct_t setsockopt_struct[2] = {
{ 4, SOL_SOCKET, SO_KEEPALIVE, 4 },
{ 4, SOL_SOCKET, SO_REUSEADDR, 4 }
};
for (i = 0; i < 2; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 4,
SCMP_A0(SCMP_CMP_GE, setsockopt_struct[i].minsockfd),
SCMP_A1(SCMP_CMP_EQ, setsockopt_struct[i].level),
SCMP_A2(SCMP_CMP_EQ, setsockopt_struct[i].optname),
SCMP_A4(SCMP_CMP_EQ, setsockopt_struct[i].optlen));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(setsockopt), 50);
if (rc < 0)
goto fail;
/* getsockopt */
struct getsockopt_struct_t {
int minsockfd;
int level;
int optname;
};
const struct getsockopt_struct_t getsockopt_struct[2] = {
{ 4, SOL_SOCKET, SO_TYPE },
{ 4, SOL_SOCKET, SO_ERROR }
};
for (i = 0; i < 2; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), 3,
SCMP_A0(SCMP_CMP_GE, getsockopt_struct[i].minsockfd),
SCMP_A1(SCMP_CMP_EQ, getsockopt_struct[i].level),
SCMP_A2(SCMP_CMP_EQ, getsockopt_struct[i].optname));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(getsockopt), 50);
if (rc < 0)
goto fail;
#endif
/* ioctl */
const int ioctl_array[3] = {
TCSETSW,
TCGETS,
TIOCGWINSZ
};
for (i = 0; i < 3; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
SCMP_A1(SCMP_CMP_EQ, ioctl_array[i]));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(ioctl), 50);
if (rc < 0)
goto fail;
/* kill */
rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(kill), 1,
SCMP_A1(SCMP_CMP_EQ, SIGTSTP));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(kill), 1,
SCMP_A1(SCMP_CMP_EQ, SIGKILL));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(kill), 50);
if (rc < 0)
goto fail;
/* prctl */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 4,
SCMP_A0(SCMP_CMP_EQ, PR_SET_NAME),
SCMP_A2(SCMP_CMP_EQ, 0),
SCMP_A3(SCMP_CMP_EQ, 0),
SCMP_A4(SCMP_CMP_EQ, 0));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(prctl), 50);
if (rc < 0)
goto fail;
/* fcntl */
const int fcntl_1arg_array[3] = {
F_GETFL,
F_GETFD,
F_SETLK
};
for (i = 0; i < 3; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1,
SCMP_A1(SCMP_CMP_EQ, fcntl_1arg_array[i]));
if (rc < 0)
goto fail;
}
struct fcntl_2args_struct_t {
int cmd;
int arg;
};
const struct fcntl_2args_struct_t fcntl_2args_struct[3] = {
{ F_SETFD, FD_CLOEXEC },
{ F_SETFL, O_RDONLY },
{ F_SETFL, O_RDONLY|O_NONBLOCK }
};
for (i = 0; i < 3; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 2,
SCMP_A1(SCMP_CMP_EQ, fcntl_2args_struct[i].cmd),
SCMP_A2(SCMP_CMP_EQ, fcntl_2args_struct[i].arg));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(fcntl), 50);
if (rc < 0)
goto fail;
/*
* mprotect
*
* Although this filter prevents making non-executable pages executable
* again, there is nothing stopping an attacker from using mmap to load
* an arbitrary executable file with PROT_READ|PROT_EXEC in the first
* place
*/
const int mprotect_prot_array[3] = {
PROT_NONE,
PROT_READ,
PROT_READ|PROT_WRITE
};
for (i = 0; i < 3; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 1,
SCMP_A2(SCMP_CMP_EQ, mprotect_prot_array[i]));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(mprotect), 60);
if (rc < 0)
goto fail;
/* mmap */
struct mmap_struct_t {
int prot;
int flags;
};
const struct mmap_struct_t mmap_anon_struct[4] = {
{ PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE },
{ PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS },
{ PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK },
{ PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS }
};
for (i = 0; i < 4; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 4,
SCMP_A2(SCMP_CMP_EQ, mmap_anon_struct[i].prot),
SCMP_A3(SCMP_CMP_EQ, mmap_anon_struct[i].flags),
SCMP_A4(SCMP_CMP_EQ, (unsigned)-1),
SCMP_A5(SCMP_CMP_EQ, 0));
if (rc < 0)
goto fail;
}
const struct mmap_struct_t mmap_noanon_struct[3] = {
{ PROT_READ, MAP_PRIVATE },
{ PROT_READ, MAP_SHARED },
{ PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE }
};
for (i = 0; i < 3; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 4,
SCMP_A2(SCMP_CMP_EQ, mmap_noanon_struct[i].prot),
SCMP_A3(SCMP_CMP_EQ, mmap_noanon_struct[i].flags),
SCMP_A4(SCMP_CMP_NE, (unsigned)-1),
SCMP_A5(SCMP_CMP_EQ, 0));
if (rc < 0)
goto fail;
}
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 4,
SCMP_A2(SCMP_CMP_EQ, PROT_READ|PROT_WRITE),
SCMP_A3(SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE),
SCMP_A4(SCMP_CMP_NE, (unsigned)-1),
SCMP_A5(SCMP_CMP_NE, 0)); /* NE 0, so can't be in the struct */
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(mmap), 60);
if (rc < 0)
goto fail;
/* madvise */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(madvise), 1,
SCMP_A2(SCMP_CMP_EQ, MADV_DONTNEED));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(madvise), 50);
if (rc < 0)
goto fail;
#ifndef __i386__
/* socket */
struct socket_struct_t {
int domain;
int type;
int proto;
};
const struct socket_struct_t socket_struct[2] = {
{ PF_INET, SOCK_STREAM, IPPROTO_TCP },
{ PF_INET, SOCK_STREAM, IPPROTO_IP }
};
for (i = 0; i < 2; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 3,
SCMP_A0(SCMP_CMP_EQ, socket_struct[i].domain),
SCMP_A1(SCMP_CMP_EQ, socket_struct[i].type),
SCMP_A2(SCMP_CMP_EQ, socket_struct[i].proto));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(socket), 50);
if (rc < 0)
goto fail;
/* connect */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 2,
SCMP_A0(SCMP_CMP_GE, 4),
SCMP_A2(SCMP_CMP_EQ, 16));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(connect), 50);
if (rc < 0)
goto fail;
/* getpeername */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpeername), 1,
SCMP_A0(SCMP_CMP_GE, 4));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(getpeername), 50);
if (rc < 0)
goto fail;
#endif
/* futex */
struct futex_struct_t {
int op;
int val;
};
const struct futex_struct_t futex_struct[5] = {
{ FUTEX_WAIT_PRIVATE, 1 },
{ FUTEX_WAIT_PRIVATE, 2 },
{ FUTEX_WAKE, 2147483647 },
{ FUTEX_WAKE_PRIVATE, 1 },
{ FUTEX_WAKE_PRIVATE, 2147483647 }
};
for (i = 0; i < 5; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 2,
SCMP_A1(SCMP_CMP_EQ, futex_struct[i].op),
SCMP_A2(SCMP_CMP_EQ, futex_struct[i].val));
if (rc < 0)
goto fail;
}
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 3,
SCMP_A1(SCMP_CMP_EQ, FUTEX_WAKE_PRIVATE),
SCMP_A2(SCMP_CMP_EQ, 2),
SCMP_A3(SCMP_CMP_EQ, NULL));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(futex), 100);
if (rc < 0)
goto fail;
/* set_robust_list */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 1,
SCMP_A1(SCMP_CMP_EQ, 24));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(set_robust_list), 50);
if (rc < 0)
goto fail;
/* setrlimit */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setrlimit), 1,
SCMP_A0(SCMP_CMP_EQ, RLIMIT_CORE));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(setrlimit), 50);
if (rc < 0)
goto fail;
/* getrlimit */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrlimit), 1,
SCMP_A0(SCMP_CMP_EQ, RLIMIT_STACK));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrlimit), 1,
SCMP_A0(SCMP_CMP_EQ, RLIMIT_CORE));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(getrlimit), 50);
if (rc < 0)
goto fail;
/* eventfd2 */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(eventfd2), 2,
SCMP_A0(SCMP_CMP_EQ, 0),
SCMP_A1(SCMP_CMP_EQ, O_NONBLOCK|O_CLOEXEC));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(eventfd2), 50);
if (rc < 0)
goto fail;
/* rt_sigprocmask */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 3,
SCMP_A0(SCMP_CMP_EQ, SIG_UNBLOCK),
SCMP_A2(SCMP_CMP_EQ, NULL),
SCMP_A3(SCMP_CMP_EQ, 8));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 1,
SCMP_A0(SCMP_CMP_EQ, SIG_SETMASK));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(rt_sigprocmask), 50);
if (rc < 0)
goto fail;
/* rt_sigaction */
const int sigaction_signal_array[13] = {
SIGALRM,
SIGCHLD,
SIGCONT,
SIGFPE,
SIGHUP,
SIGINT,
SIGPIPE,
SIGQUIT,
SIGTERM,
SIGTRAP,
SIGUSR1,
SIGUSR2,
SIGWINCH
};
for (i = 0; i < 13; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 2,
SCMP_A0(SCMP_CMP_EQ, sigaction_signal_array[i]),
SCMP_A3(SCMP_CMP_EQ, 8));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(rt_sigaction), 50);
if (rc < 0)
goto fail;
#ifndef __i386__
/* sendto */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendto), 4,
SCMP_A0(SCMP_CMP_GE, 4),
SCMP_A3(SCMP_CMP_EQ, 0),
SCMP_A4(SCMP_CMP_EQ, NULL),
SCMP_A5(SCMP_CMP_EQ, 0));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(sendto), 70);
if (rc < 0)
goto fail;
/* recvfrom */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 5,
SCMP_A0(SCMP_CMP_GE, 4),
SCMP_A2(SCMP_CMP_EQ, 8),
SCMP_A3(SCMP_CMP_EQ, 0),
SCMP_A4(SCMP_CMP_EQ, NULL),
SCMP_A5(SCMP_CMP_EQ, NULL));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(recvfrom), 70);
if (rc < 0)
goto fail;
#endif
/* access */
const int access_mode_array[3] = {
R_OK,
F_OK,
X_OK
};
for (i = 0; i < 3; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 1,
SCMP_A1(SCMP_CMP_EQ, access_mode_array[i]));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(access), 60);
if (rc < 0)
goto fail;
/* write */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_LT, 3),
SCMP_A2(SCMP_CMP_LE, 4096));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_GT, 3),
SCMP_A2(SCMP_CMP_LE, 4096));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_EQ, 3),
/* SCMP_A1(SCMP_CMP_EQ, "\1\0\0\0\0\0\0\0"), */
SCMP_A2(SCMP_CMP_EQ, 8));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(write), 200);
if (rc < 0)
goto fail;
/* read */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 2,
SCMP_A0(SCMP_CMP_EQ, 0),
SCMP_A2(SCMP_CMP_EQ, 256));
if (rc < 0)
goto fail;
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 2,
SCMP_A0(SCMP_CMP_GE, 3),
SCMP_A2(SCMP_CMP_LE, 16384));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(read), 200);
if (rc < 0)
goto fail;
/* open */
const int open_flag_array[6] = {
O_RDONLY,
O_RDONLY|O_CLOEXEC,
O_RDONLY|O_NOCTTY|O_NONBLOCK,
O_WRONLY|O_CREAT|O_APPEND,
O_WRONLY|O_CREAT|O_TRUNC,
O_RDWR|O_CREAT|O_TRUNC
};
for (i = 0; i < 6; i++) {
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1,
SCMP_A1(SCMP_CMP_EQ, open_flag_array[i]));
if (rc < 0)
goto fail;
}
rc = seccomp_syscall_priority(ctx, SCMP_SYS(open), 60);
if (rc < 0)
goto fail;
/* openat */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 2,
SCMP_A0(SCMP_CMP_EQ, AT_FDCWD),
SCMP_A2(SCMP_CMP_EQ, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(openat), 50);
if (rc < 0)
goto fail;
/* readlink */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(readlink), 1,
SCMP_A2(SCMP_CMP_EQ, 4095));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(readlink), 50);
if (rc < 0)
goto fail;
/* getdents */
rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(getdents), 1,
SCMP_A2(SCMP_CMP_EQ, 32768));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(getdents), 70);
if (rc < 0)
goto fail;
/* clock_gettime */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 1,
SCMP_A0(SCMP_CMP_EQ, CLOCK_PROCESS_CPUTIME_ID));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(clock_gettime), 60);
if (rc < 0)
goto fail;
/* getrusage */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrusage), 1,
SCMP_A0(SCMP_CMP_EQ, 0)); /* RUSAGE_SELF */
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(getrusage), 50);
if (rc < 0)
goto fail;
/* select */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(select), 2,
SCMP_A2(SCMP_CMP_EQ, NULL),
SCMP_A3(SCMP_CMP_EQ, NULL));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(select), 200);
if (rc < 0)
goto fail;
/* poll */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 2,
SCMP_A1(SCMP_CMP_LE, 3),
SCMP_A2(SCMP_CMP_GE, 0),
SCMP_A2(SCMP_CMP_LE, 1000));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(poll), 200);
if (rc < 0)
goto fail;
/* wait4 */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(wait4), 2,
SCMP_A2(SCMP_CMP_EQ, WNOHANG),
SCMP_A3(SCMP_CMP_EQ, NULL));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(wait4), 50);
if (rc < 0)
goto fail;
/* tgkill */
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(tgkill), 2,
SCMP_A0(SCMP_CMP_EQ, getpid()),
SCMP_A2(SCMP_CMP_EQ, SIGABRT));
if (rc < 0)
goto fail;
rc = seccomp_syscall_priority(ctx, SCMP_SYS(tgkill), 50);
if (rc < 0)
goto fail;
/* apply filters */
rc = seccomp_load(ctx);
if (rc < 0)
goto fail;
/* seccomp has been seccessfully loaded and is now enforced */
return;
fail:
seccomp_release(ctx);
fprintf(stderr, "Could not load the sandbox (libseccomp error %d)\n", -rc);
exit(1);
}

6
src/core/sandbox.h Normal file
View file

@ -0,0 +1,6 @@
extern void create_namespaces(void);
extern void drop_privileges(void);
extern void enable_seccomp_sandbox(void);
extern void enforce_resource_limits(void);
extern int sandbox;

View file

@ -62,6 +62,9 @@ static void cmd_upgrade(const char *data)
char *session_file, *str; char *session_file, *str;
char *binary; char *binary;
if (sandbox)
return;
if (*data == '\0') if (*data == '\0')
data = irssi_binary; data = irssi_binary;

View file

@ -553,6 +553,9 @@ static void cmd_exec(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
char *args; char *args;
void *free_arg; void *free_arg;
if (sandbox)
return;
g_return_if_fail(data != NULL); g_return_if_fail(data != NULL);
if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |

View file

@ -28,6 +28,7 @@
#include "settings.h" #include "settings.h"
#include "session.h" #include "session.h"
#include "servers.h" #include "servers.h"
#include "sandbox.h"
#include "printtext.h" #include "printtext.h"
#include "fe-common-core.h" #include "fe-common-core.h"
@ -74,11 +75,20 @@ void mainwindow_activity_deinit(void);
void mainwindows_layout_init(void); void mainwindows_layout_init(void);
void mainwindows_layout_deinit(void); void mainwindows_layout_deinit(void);
#ifdef HAVE_SECCOMP
void create_namespaces(void);
void drop_privileges(void);
void enforce_resource_limits(void);
void enable_seccomp_sandbox(void);
#endif
void term_dummy_init(void); void term_dummy_init(void);
void term_dummy_deinit(void); void term_dummy_deinit(void);
static int dirty, full_redraw, dummy; static int dirty, full_redraw, dummy;
int sandbox = 0;
static GMainLoop *main_loop; static GMainLoop *main_loop;
int quitting; int quitting;
@ -278,6 +288,7 @@ int main(int argc, char **argv)
static GOptionEntry options[] = { static GOptionEntry options[] = {
{ "dummy", 'd', 0, G_OPTION_ARG_NONE, &dummy, "Use the dummy terminal mode", NULL }, { "dummy", 'd', 0, G_OPTION_ARG_NONE, &dummy, "Use the dummy terminal mode", NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Display irssi version", NULL }, { "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Display irssi version", NULL },
{ "sandbox", 's', 0, G_OPTION_ARG_NONE, &sandbox, "Enable the sandbox", NULL },
{ NULL } { NULL }
}; };
int loglev; int loglev;
@ -293,6 +304,18 @@ int main(int argc, char **argv)
return 0; return 0;
} }
if (sandbox) {
#ifdef HAVE_SECCOMP
create_namespaces();
drop_privileges();
enforce_resource_limits();
enable_seccomp_sandbox();
#else
fprintf(stderr, "This build does not support sandboxing\n");
return 1;
#endif
}
srand(time(NULL)); srand(time(NULL));
dummy = FALSE; dummy = FALSE;