Use the new seccomp BPF functionality in Linux 3.5 to disallow setuid(0), setgid(0) and all similar calls, somewhat decreasing the impact of an attack managing to sneak before the setuid() call. Note that it is not possible to restrict setgroups() with seccomp BPF, since the group list is sent in through a pointer. Thus, a rogue process can still add itself to any supplementary group (including the root group), but at least it disallows writing to non-group-writable files owned by uid 0. Index: httpd-2.4.2/server/mpm/itk/itk.c =================================================================== --- httpd-2.4.2.orig/server/mpm/itk/itk.c +++ httpd-2.4.2/server/mpm/itk/itk.c @@ -60,6 +60,8 @@ #include "ap_mmn.h" #include "apr_poll.h" +#include "seccomp.h" + #ifdef HAVE_TIME_H #include #endif @@ -111,6 +113,10 @@ static int ap_daemons_to_start=0; static int ap_daemons_min_free=0; static int ap_daemons_max_free=0; static int ap_daemons_limit=0; /* MaxRequestWorkers */ +static uid_t ap_itk_min_uid=1; +static uid_t ap_itk_max_uid=UINT_MAX; +static gid_t ap_itk_min_gid=1; +static gid_t ap_itk_max_gid=UINT_MAX; static int server_limit = 0; static int mpm_state = AP_MPMQ_STARTING; static ap_pod_t *pod; @@ -583,8 +589,10 @@ static void child_main(int child_num_arg * query processing later. * * Note that since we still have CAP_SETUID, an attacker can setuid(0) - * and get around this. + * and get around this. Thus, we disallow setuid(0) if the platform + * allows it. */ + restrict_setuid_range(ap_itk_min_uid, ap_itk_max_uid, ap_itk_min_gid, ap_itk_max_gid); if (prctl(PR_SET_KEEPCAPS, 1)) { ap_log_error(APLOG_MARK, APLOG_EMERG, errno, NULL, "prctl(PR_SET_KEEPCAPS, 1) failed"); clean_child_exit(APEXIT_CHILDFATAL); @@ -1568,12 +1576,20 @@ static int itk_post_perdir_config(reques if (wanted_uid != -1 && wanted_gid != -1 && (getuid() != wanted_uid || getgid() != wanted_gid)) { if (setgid(wanted_gid)) { _DBG("setgid(%d): %s", wanted_gid, strerror(errno)); + if (wanted_gid < ap_itk_min_gid || wanted_gid > ap_itk_max_gid) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, + "This is most likely due to the current LimitGIDRange setting."); + } err = 1; } else if (initgroups(wanted_username, wanted_gid)) { _DBG("initgroups(%s, %d): %s", wanted_username, wanted_gid, strerror(errno)); err = 1; } else if (setuid(wanted_uid)) { _DBG("setuid(%d): %s", wanted_uid, strerror(errno)); + if (wanted_uid < ap_itk_min_uid || wanted_uid > ap_itk_max_uid) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, + "This is most likely due to the current LimitUIDRange setting."); + } err = 1; } else { #if HAVE_LIBCAP @@ -1727,6 +1743,30 @@ static const char *assign_user_id (cmd_p return NULL; } +static const char *limit_uid_range(cmd_parms *cmd, void *dummy, const char *min_arg, const char *max_arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_itk_min_uid = atoi(min_arg); + ap_itk_max_uid = atoi(max_arg); + return NULL; +} + +static const char *limit_gid_range(cmd_parms *cmd, void *dummy, const char *min_arg, const char *max_arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_itk_min_gid = atoi(min_arg); + ap_itk_max_gid = atoi(max_arg); + return NULL; +} + static const command_rec itk_cmds[] = { LISTEN_COMMANDS, AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, @@ -1743,6 +1783,13 @@ AP_INIT_TAKE1("ServerLimit", set_server_ "Maximum value of MaxRequestWorkers for this run of Apache"), AP_INIT_TAKE2("AssignUserID", assign_user_id, NULL, RSRC_CONF|ACCESS_CONF, "Tie a virtual host to a specific child process."), +AP_INIT_TAKE2("LimitUIDRange", limit_uid_range, NULL, RSRC_CONF, + "If seccomp v2 is available (Linux 3.5.0+), limit the process's possible " + "uid to the given range (inclusive endpoints)"), +AP_INIT_TAKE2("LimitGIDRange", limit_gid_range, NULL, RSRC_CONF, + "If seccomp v2 is available (Linux 3.5.0+), limit the process's possible " + "primary gid to the given range (inclusive endpoints). " + "Note that this does not restrict supplemental gids!"), AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, { NULL } }; Index: httpd-2.4.2/server/mpm/itk/config3.m4 =================================================================== --- httpd-2.4.2.orig/server/mpm/itk/config3.m4 +++ httpd-2.4.2/server/mpm/itk/config3.m4 @@ -1 +1 @@ -APACHE_MPM_MODULE(itk, $enable_mpm_itk) +APACHE_MPM_MODULE(itk, $enable_mpm_itk, itk.lo seccomp.lo) Index: httpd-2.4.2/server/mpm/itk/seccomp.c =================================================================== --- /dev/null +++ httpd-2.4.2/server/mpm/itk/seccomp.c @@ -0,0 +1,173 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Portions copyright 2005-2012 Steinar H. Gunderson . + * Licensed under the same terms as the rest of Apache. + */ + +#include "ap_config.h" +#include "httpd.h" +#include "http_log.h" +#include "http_main.h" + +#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__)) + +#define SECCOMP_BPF_SUPPORTED 1 + +#include +#include +#include +#include +#include +#include + +/* These definitions are from , which we do not include + * because it is not very commonly installed yet. + */ +#define SECCOMP_RET_KILL 0x00000000U +#define SECCOMP_RET_ERRNO 0x00050000U +#define SECCOMP_RET_ALLOW 0x7fff0000U +#define SECCOMP_MODE_FILTER 2 +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; + +#define syscall_arg(_n) (offsetof(struct seccomp_data, args[_n])) +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define arch_nr (offsetof(struct seccomp_data, arch)) + +/* Note that we assume little-endian in the BPF (we assume the first 32 bits of + * the argument contain the lower 32 bits, whether we are on 32- or 64-bit). + */ +#if defined(__i386__) +#define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__amd64__) +#define ARCH_NR AUDIT_ARCH_X86_64 +#endif + +#endif + +#if SECCOMP_BPF_SUPPORTED + +static int apply_seccomp_filter(struct sock_filter *filter, int len) +{ + struct sock_fprog seccomp_prog = { + .len = len, + .filter = filter, + }; + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &seccomp_prog) != 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Installing seccomp filter failed (probably due to too old kernel); " + "unable to restrict setuid privileges. Error was: %s", + strerror(errno)); + return 1; + } else { + return 0; + } +} + +static void add_bpf_stmt(struct sock_filter *filter, int *pos, __u16 code, __u32 k) +{ + struct sock_filter stmt = BPF_STMT(code, k); + filter[*pos] = stmt; + ++*pos; +} + +static void add_bpf_jump(struct sock_filter *filter, int *pos, __u16 code, __u32 k, __u8 jt, __u8 jf) +{ + struct sock_filter stmt = BPF_JUMP(code, k, jt, jf); + filter[*pos] = stmt; + ++*pos; +} + +static int limit_syscall_range(int syscall_to_match, int nr_args, int min, int max) +{ + static struct sock_filter syscall_filter[BPF_MAXINSNS]; + + int pos = 0; + + /* If we don't match the syscall number, return immediately. */ + add_bpf_stmt(syscall_filter, &pos, BPF_LD + BPF_W + BPF_ABS, syscall_nr); + add_bpf_jump(syscall_filter, &pos, BPF_JMP + BPF_JEQ + BPF_K, syscall_to_match, 1, 0); + add_bpf_stmt(syscall_filter, &pos, BPF_RET + BPF_K, SECCOMP_RET_ALLOW); + + for (int i = 0; i < nr_args; ++i) { + add_bpf_stmt(syscall_filter, &pos, BPF_LD + BPF_W + BPF_ABS, syscall_arg(i)); + add_bpf_jump(syscall_filter, &pos, BPF_JMP + BPF_JGE + BPF_K, min, 1, 0); + add_bpf_stmt(syscall_filter, &pos, BPF_RET + BPF_K, SECCOMP_RET_ERRNO | EPERM); + add_bpf_jump(syscall_filter, &pos, BPF_JMP + BPF_JGT + BPF_K, max, 0, 1); + add_bpf_stmt(syscall_filter, &pos, BPF_RET + BPF_K, SECCOMP_RET_ERRNO | EPERM); + } + + add_bpf_stmt(syscall_filter, &pos, BPF_RET + BPF_K, SECCOMP_RET_ALLOW); + return apply_seccomp_filter(syscall_filter, pos); +} + +#endif + +void restrict_setuid_range(uid_t min_uid, uid_t max_uid, gid_t min_gid, gid_t max_gid) +{ +#if SECCOMP_BPF_SUPPORTED + uid_t min_uid16 = (min_uid > 65535) ? 65535 : min_uid; + uid_t max_uid16 = (max_uid > 65535) ? 65535 : max_uid; + gid_t min_gid16 = (min_gid > 65535) ? 65535 : min_gid; + gid_t max_gid16 = (max_gid > 65535) ? 65535 : max_gid; + + /* Apply a seccomp BPF to ourselves that disallows all setuid- and + * setgid-like calls if the first argument is 0. The list of calls comes from + * the descriptions of CAP_SETUID and CAP_SETGID in capabilities(7), although + * CAP_SETGID is underdocumented. + */ + struct sock_filter arch_filter[] = { + /* Validate that the syscall is of the right architecture, + * so that an attacker cannot circumvent our syscall protection + * by changing to a different personality. + */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0), + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL), + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), + }; + if (apply_seccomp_filter(arch_filter, sizeof(arch_filter) / sizeof(arch_filter[0])) != 0) { + return; + } + limit_syscall_range(__NR_setfsuid32, 1, min_uid, max_uid); + limit_syscall_range(__NR_setuid32, 1, min_uid, max_uid); + limit_syscall_range(__NR_setreuid32, 2, min_uid, max_uid); + limit_syscall_range(__NR_setresuid32, 3, min_uid, max_uid); + + limit_syscall_range(__NR_setfsuid, 1, min_uid16, max_uid16); + limit_syscall_range(__NR_setuid, 1, min_uid16, max_uid16); + limit_syscall_range(__NR_setreuid, 2, min_uid16, max_uid16); + limit_syscall_range(__NR_setresuid, 3, min_uid16, max_uid16); + + limit_syscall_range(__NR_setfsgid32, 1, min_gid, max_gid); + limit_syscall_range(__NR_setgid32, 1, min_gid, max_gid); + limit_syscall_range(__NR_setregid32, 2, min_gid, max_gid); + limit_syscall_range(__NR_setresgid32, 3, min_gid, max_gid); + + limit_syscall_range(__NR_setfsgid, 1, min_gid16, max_gid16); + limit_syscall_range(__NR_setgid, 1, min_gid16, max_gid16); + limit_syscall_range(__NR_setregid, 2, min_gid16, max_gid16); + limit_syscall_range(__NR_setresgid, 3, min_gid16, max_gid16); +#else + ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Your platform or architecture does not support seccomp v2; " + "unable to restrict setuid privileges."); +#endif +} Index: httpd-2.4.2/server/mpm/itk/seccomp.h =================================================================== --- /dev/null +++ httpd-2.4.2/server/mpm/itk/seccomp.h @@ -0,0 +1,35 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Portions copyright 2005-2012 Steinar H. Gunderson . + * Licensed under the same terms as the rest of Apache. + */ + +/** + * @file itk/seccomp.h + * @brief Utility functions for seccomp support + * + * @defgroup APACHE_MPM_ITK Apache ITK + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_SECCOMP_H +#define APACHE_MPM_SECCOMP_H + +void restrict_setuid_range(uid_t min_uid, uid_t max_uid, gid_t min_gid, gid_t max_gid); + +#endif /* AP_MPM_SECCOMP_H */ +/** @} */ Index: httpd-2.4.2/server/mpm/itk/modules.mk =================================================================== --- httpd-2.4.2.orig/server/mpm/itk/modules.mk +++ httpd-2.4.2/server/mpm/itk/modules.mk @@ -1,5 +1,5 @@ -libitk.la: itk.lo - $(MOD_LINK) itk.lo +libitk.la: itk.lo seccomp.lo + $(MOD_LINK) itk.lo seccomp.lo DISTCLEAN_TARGETS = modules.mk static = libitk.la shared =