diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
commit | 5dff2d61cc1c27747ee398e04d8e02843aabb1f8 (patch) | |
tree | a67c336b406c8227bac912beb74a1ad3cdc55100 /modules/arch/unix | |
parent | Initial commit. (diff) | |
download | apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.tar.xz apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.zip |
Adding upstream version 2.4.38.upstream/2.4.38
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/arch/unix')
-rw-r--r-- | modules/arch/unix/Makefile.in | 3 | ||||
-rw-r--r-- | modules/arch/unix/config5.m4 | 24 | ||||
-rw-r--r-- | modules/arch/unix/mod_privileges.c | 588 | ||||
-rw-r--r-- | modules/arch/unix/mod_unixd.c | 433 | ||||
-rw-r--r-- | modules/arch/unix/mod_unixd.h | 41 |
5 files changed, 1089 insertions, 0 deletions
diff --git a/modules/arch/unix/Makefile.in b/modules/arch/unix/Makefile.in new file mode 100644 index 0000000..167b343 --- /dev/null +++ b/modules/arch/unix/Makefile.in @@ -0,0 +1,3 @@ + +include $(top_srcdir)/build/special.mk + diff --git a/modules/arch/unix/config5.m4 b/modules/arch/unix/config5.m4 new file mode 100644 index 0000000..77027a8 --- /dev/null +++ b/modules/arch/unix/config5.m4 @@ -0,0 +1,24 @@ + +APACHE_MODPATH_INIT(arch/unix) + +if ap_mpm_is_enabled "worker" \ + || ap_mpm_is_enabled "event" \ + || ap_mpm_is_enabled "prefork"; then + unixd_mods_enable=yes +else + unixd_mods_enable=no +fi + +APACHE_MODULE(unixd, unix specific support, , , $unixd_mods_enable) +APACHE_MODULE(privileges, Per-virtualhost Unix UserIDs and enhanced security for Solaris, , , no, [ + AC_CHECK_HEADERS(priv.h, [ap_HAVE_PRIV_H="yes"], [ap_HAVE_PRIV_H="no"]) + if test $ap_HAVE_PRIV_H = "no"; then + AC_MSG_WARN([Your system does not support privileges.]) + enable_privileges="no" + fi +]) + +APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) + +APACHE_MODPATH_FINISH + diff --git a/modules/arch/unix/mod_privileges.c b/modules/arch/unix/mod_privileges.c new file mode 100644 index 0000000..fede3d8 --- /dev/null +++ b/modules/arch/unix/mod_privileges.c @@ -0,0 +1,588 @@ +/* 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. + */ + +#include <priv.h> +#include <sys/types.h> +#include <unistd.h> + +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_log.h" +#include "mpm_common.h" +#include "ap_mpm.h" +#include "apr_strings.h" + +/* TODO - get rid of unixd dependency */ +#include "unixd.h" + +#define CFG_CHECK(x) if ((x) == -1) { \ + char msgbuf[128]; \ + apr_strerror(errno, msgbuf, sizeof(msgbuf)); \ + return apr_pstrdup(cmd->pool, msgbuf); \ +} +#define CR_CHECK(x, y) if (x == -1) \ + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, 0, y \ + "Failed to initialise privileges") + +module AP_MODULE_DECLARE_DATA privileges_module; + +/* #define BIG_SECURITY_HOLE 1 */ + +typedef enum { PRIV_UNSET, PRIV_FAST, PRIV_SECURE, PRIV_SELECTIVE } priv_mode; + +typedef struct { + priv_set_t *priv; + priv_set_t *child_priv; + uid_t uid; + gid_t gid; + priv_mode mode; +} priv_cfg; + +typedef struct { + priv_mode mode; +} priv_dir_cfg; + +static priv_set_t *priv_setid; +static priv_set_t *priv_default = NULL; +static int dtrace_enabled = 0; + +static apr_status_t priv_cfg_cleanup(void *CFG) +{ + priv_cfg *cfg = CFG; + priv_freeset(cfg->priv); + priv_freeset(cfg->child_priv); + return APR_SUCCESS; +} +static void *privileges_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD) +{ + /* inherit the mode if it's not set; the rest won't be inherited */ + priv_cfg *base = BASE; + priv_cfg *add = ADD; + priv_cfg *ret = apr_pmemdup(pool, add, sizeof(priv_cfg)); + ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode; + return ret; +} +static void *privileges_create_cfg(apr_pool_t *pool, server_rec *s) +{ + priv_cfg *cfg = apr_palloc(pool, sizeof(priv_cfg)); + + /* Start at basic privileges all round. */ + cfg->priv = priv_str_to_set("basic", ",", NULL); + cfg->child_priv = priv_str_to_set("basic", ",", NULL); + + /* By default, run in secure vhost mode. + * That means dropping basic privileges we don't usually need. + */ + CR_CHECK(priv_delset(cfg->priv, PRIV_FILE_LINK_ANY), APLOGNO(03160)); + CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_INFO), APLOGNO(03161)); + CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_SESSION), APLOGNO(03162)); + +/* Hmmm, should CGI default to secure too ? */ +/* + CR_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY), APLOGNO(03163)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO), APLOGNO(03164)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION), APLOGNO(03165)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK), APLOGNO(03166)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC), APLOGNO(03167)); +*/ + + /* we´ll use 0 for unset */ + cfg->uid = 0; + cfg->gid = 0; + cfg->mode = PRIV_UNSET; + apr_pool_cleanup_register(pool, cfg, priv_cfg_cleanup, + apr_pool_cleanup_null); + + /* top-level default_priv wants the top-level cfg */ + if (priv_default == NULL) { + priv_default = cfg->priv; + } + return cfg; +} +static void *privileges_create_dir_cfg(apr_pool_t *pool, char *dummy) +{ + priv_dir_cfg *cfg = apr_palloc(pool, sizeof(priv_dir_cfg)); + cfg->mode = PRIV_UNSET; + return cfg; +} +static void *privileges_merge_dir_cfg(apr_pool_t *pool, void *BASE, void *ADD) +{ + priv_dir_cfg *base = BASE; + priv_dir_cfg *add = ADD; + priv_dir_cfg *ret = apr_palloc(pool, sizeof(priv_dir_cfg)); + ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode; + return ret; +} + +static apr_status_t privileges_end_req(void *data) +{ + request_rec *r = data; + priv_cfg *cfg = ap_get_module_config(r->server->module_config, + &privileges_module); + priv_dir_cfg *dcfg = ap_get_module_config(r->per_dir_config, + &privileges_module); + + /* ugly hack: grab default uid and gid from unixd */ + extern unixd_config_rec ap_unixd_config; + + /* If we forked a child, we dropped privilege to revert, so + * all we can do now is exit + */ + if ((cfg->mode == PRIV_SECURE) || + ((cfg->mode == PRIV_SELECTIVE) && (dcfg->mode == PRIV_SECURE))) { + exit(0); + } + + /* if either user or group are not the default, restore them */ + if (cfg->uid || cfg->gid) { + if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02136) + "PRIV_ON failed restoring default user/group"); + } + if (cfg->uid && (setuid(ap_unixd_config.user_id) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02137) + "Error restoring default userid"); + } + if (cfg->gid && (setgid(ap_unixd_config.group_id) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02138) + "Error restoring default group"); + } + } + + /* restore default privileges */ + if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_default) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02139) + "Error restoring default privileges"); + } + return APR_SUCCESS; +} +static int privileges_req(request_rec *r) +{ + /* secure mode: fork a process to handle the request */ + apr_proc_t proc; + apr_status_t rv; + int exitcode; + apr_exit_why_e exitwhy; + int fork_req; + priv_cfg *cfg = ap_get_module_config(r->server->module_config, + &privileges_module); + + void *breadcrumb = ap_get_module_config(r->request_config, + &privileges_module); + + if (!breadcrumb) { + /* first call: this is the vhost */ + fork_req = (cfg->mode == PRIV_SECURE); + + /* set breadcrumb */ + ap_set_module_config(r->request_config, &privileges_module, &cfg->mode); + + /* If we have per-dir config, defer doing anything */ + if ((cfg->mode == PRIV_SELECTIVE)) { + /* Defer dropping privileges 'til we have a directory + * context that'll tell us whether to fork. + */ + return DECLINED; + } + } + else { + /* second call is for per-directory. */ + priv_dir_cfg *dcfg; + if ((cfg->mode != PRIV_SELECTIVE)) { + /* Our fate was already determined for the vhost - + * nothing to do per-directory + */ + return DECLINED; + } + dcfg = ap_get_module_config(r->per_dir_config, &privileges_module); + fork_req = (dcfg->mode == PRIV_SECURE); + } + + if (fork_req) { + rv = apr_proc_fork(&proc, r->pool); + switch (rv) { + case APR_INPARENT: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02140) + "parent waiting for child"); + /* FIXME - does the child need to run synchronously? + * esp. if we enable mod_privileges with threaded MPMs? + * We do need at least to ensure r outlives the child. + */ + rv = apr_proc_wait(&proc, &exitcode, &exitwhy, APR_WAIT); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02141) "parent: child %s", + (rv == APR_CHILD_DONE) ? "done" : "notdone"); + + /* The child has taken responsibility for reading all input + * and sending all output. So we need to bow right out, + * and even abandon "normal" housekeeping. + */ + r->eos_sent = 1; + apr_table_unset(r->headers_in, "Content-Type"); + apr_table_unset(r->headers_in, "Content-Length"); + /* Testing with ab and 100k requests reveals no nasties + * so I infer we're not leaking anything like memory + * or file descriptors. That's nice! + */ + return DONE; + case APR_INCHILD: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02142) "In child!"); + break; /* now we'll drop privileges in the child */ + default: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02143) + "Failed to fork secure child process!"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* OK, now drop privileges. */ + + /* cleanup should happen even if something fails part-way through here */ + apr_pool_cleanup_register(r->pool, r, privileges_end_req, + apr_pool_cleanup_null); + /* set user and group if configured */ + if (cfg->uid || cfg->gid) { + if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02144) + "No privilege to set user/group"); + } + /* if we should be able to set these but can't, it could be + * a serious security issue. Bail out rather than risk it! + */ + if (cfg->uid && (setuid(cfg->uid) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02145) + "Error setting userid"); + return HTTP_INTERNAL_SERVER_ERROR; + } + if (cfg->gid && (setgid(cfg->gid) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02146) + "Error setting group"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + /* set vhost's privileges */ + if (setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02147) + "Error setting effective privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* ... including those of any subprocesses */ + if (setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02148) + "Error setting inheritable privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + if (setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02149) + "Error setting limit privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* If we're in a child process, drop down PPERM too */ + if (fork_req) { + if (setppriv(PRIV_SET, PRIV_PERMITTED, cfg->priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02150) + "Error setting permitted privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + return OK; +} +#define PDROP_CHECK(x) if (x == -1) { \ + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s, APLOGNO(02151) \ + "Error dropping privileges"); \ + return !OK; \ + } + +static int privileges_drop_first(apr_pool_t *pool, server_rec *s) +{ + /* We need to set privileges before mod_unixd, + * 'cos otherwise setuid will wipe our privilege to do so + */ + priv_cfg *spcfg; + server_rec *sp; + priv_set_t *ppriv = priv_allocset(); + + /* compute ppriv from the union of all the vhosts plus setid */ + priv_copyset(priv_setid, ppriv); + for (sp = s; sp != NULL; sp=sp->next) { + spcfg = ap_get_module_config(sp->module_config, &privileges_module); + priv_union(spcfg->priv, ppriv); + } + PDROP_CHECK(setppriv(PRIV_SET, PRIV_PERMITTED, ppriv)) + PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, ppriv)) + priv_freeset(ppriv); + + return OK; +} +static int privileges_drop_last(apr_pool_t *pool, server_rec *s) +{ + /* Our config stuff has set the privileges we need, so now + * we just set them to those of the parent server_rec + * + * This has to happen after mod_unixd, 'cos mod_unixd needs + * privileges we drop here. + */ + priv_cfg *cfg = ap_get_module_config(s->module_config, &privileges_module); + + /* defaults - the default vhost */ + PDROP_CHECK(setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv)) + PDROP_CHECK(setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv)) + PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv)) + + return OK; +} +static apr_status_t privileges_term(void *rec) +{ + priv_freeset(priv_setid); + return APR_SUCCESS; +} +static int privileges_postconf(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + priv_cfg *cfg; + server_rec *sp; + + /* if we have dtrace enabled, merge it into everything */ + if (dtrace_enabled) { + for (sp = s; sp != NULL; sp = sp->next) { + cfg = ap_get_module_config(sp->module_config, &privileges_module); + CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_KERNEL), APLOGNO(03168)); + CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_PROC), APLOGNO(03169)); + CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_USER), APLOGNO(03170)); + CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_KERNEL), APLOGNO(03171)); + CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_PROC), APLOGNO(03172)); + CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_USER), APLOGNO(03173)); + } + CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_KERNEL), APLOGNO(03174)); + CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_PROC), APLOGNO(03175)); + CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_USER), APLOGNO(03176)); + } + + /* set up priv_setid for per-request use */ + priv_setid = priv_allocset(); + apr_pool_cleanup_register(pconf, NULL, privileges_term, + apr_pool_cleanup_null); + priv_emptyset(priv_setid); + if (priv_addset(priv_setid, PRIV_PROC_SETID) == -1) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, errno, ptemp, APLOGNO(02152) + "priv_addset"); + return !OK; + } + return OK; +} +static int privileges_init(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + /* refuse to work if the MPM is threaded */ + int threaded; + int rv = ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_NOTICE, rv, ptemp, APLOGNO(02153) + "mod_privileges: unable to determine MPM characteristics." + " Please ensure you are using a non-threaded MPM " + "with this module."); + } + if (threaded) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, ptemp, APLOGNO(02154) + "mod_privileges is not compatible with a threaded MPM."); + return !OK; + } + return OK; +} +static void privileges_hooks(apr_pool_t *pool) +{ + ap_hook_post_read_request(privileges_req, NULL, NULL, + APR_HOOK_REALLY_FIRST); + ap_hook_header_parser(privileges_req, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_drop_privileges(privileges_drop_first, NULL, NULL, APR_HOOK_FIRST); + ap_hook_drop_privileges(privileges_drop_last, NULL, NULL, APR_HOOK_LAST); + ap_hook_post_config(privileges_postconf, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_config(privileges_init, NULL, NULL, APR_HOOK_FIRST); +} + +static const char *vhost_user(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + cfg->uid = ap_uname2id(arg); + if (cfg->uid == 0) { + return apr_pstrcat(cmd->pool, "Invalid userid for VHostUser: ", + arg, NULL); + } + return NULL; +} +static const char *vhost_group(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + cfg->gid = ap_gname2id(arg); + if (cfg->uid == 0) { + return apr_pstrcat(cmd->pool, "Invalid groupid for VHostGroup: ", + arg, NULL); + } + return NULL; +} +static const char *vhost_secure(cmd_parms *cmd, void *dir, int arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + if (!arg) { + /* add basic privileges, excluding those covered by cgimode */ + CFG_CHECK(priv_addset(cfg->priv, PRIV_FILE_LINK_ANY)); + CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_INFO)); + CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_SESSION)); + } + return NULL; +} +static const char *vhost_cgimode(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + if (!strcasecmp(arg, "on")) { + /* default - nothing to do */ + } + else if (!strcasecmp(arg, "off")) { + /* drop fork+exec privs */ + CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_FORK)); + CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_EXEC)); + } + else if (!strcasecmp(arg, "secure")) { + /* deny privileges to CGI procs */ + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION)); + } + else { + return "VHostCGIMode must be On, Off or Secure"; + } + + return NULL; +} +static const char *dtraceenable(cmd_parms *cmd, void *dir, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + dtrace_enabled = arg; + return NULL; +} + +static const char *privs_mode(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_mode mode = PRIV_UNSET; + if (!strcasecmp(arg, "FAST")) { + mode = PRIV_FAST; + } + else if (!strcasecmp(arg, "SECURE")) { + mode = PRIV_SECURE; + } + else if (!strcasecmp(arg, "SELECTIVE")) { + mode = PRIV_SELECTIVE; + } + + if (cmd->path) { + /* In a directory context, set the per_dir_config */ + priv_dir_cfg *cfg = dir; + cfg->mode = mode; + if ((mode == PRIV_UNSET) || (mode == PRIV_SELECTIVE)) { + return "PrivilegesMode in a Directory context must be FAST or SECURE"; + } + } + else { + /* In a global or vhost context, set the server config */ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + cfg->mode = mode; + if (mode == PRIV_UNSET) { + return "PrivilegesMode must be FAST, SECURE or SELECTIVE"; + } + } + return NULL; +} + +#ifdef BIG_SECURITY_HOLE +static const char *vhost_privs(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + const char *priv = arg; + + if (*priv == '-') { + CFG_CHECK(priv_delset(cfg->priv, priv+1)); + } + else if (*priv == '+') { + CFG_CHECK(priv_addset(cfg->priv, priv+1)); + } + else { + priv_emptyset(cfg->priv); + CFG_CHECK(priv_addset(cfg->priv, priv)); + } + return NULL; +} +static const char *vhost_cgiprivs(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + const char *priv = arg; + if (*priv == '-') { + CFG_CHECK(priv_delset(cfg->child_priv, priv+1)); + } + else if (*priv == '+') { + CFG_CHECK(priv_addset(cfg->child_priv, priv+1)); + } + else { + priv_emptyset(cfg->child_priv); + CFG_CHECK(priv_addset(cfg->child_priv, priv)); + } + return NULL; +} +#endif +static const command_rec privileges_cmds[] = { + AP_INIT_TAKE1("VHostUser", vhost_user, NULL, RSRC_CONF, + "Userid under which the virtualhost will run"), + AP_INIT_TAKE1("VHostGroup", vhost_group, NULL, RSRC_CONF, + "Group under which the virtualhost will run"), + AP_INIT_FLAG("VHostSecure", vhost_secure, NULL, RSRC_CONF, + "Run in enhanced security mode (default ON)"), + AP_INIT_TAKE1("VHostCGIMode", vhost_cgimode, NULL, RSRC_CONF, + "Enable fork+exec for this virtualhost (Off|Secure|On)"), + AP_INIT_FLAG("DTracePrivileges", dtraceenable, NULL, RSRC_CONF, + "Enable DTrace"), + AP_INIT_TAKE1("PrivilegesMode", privs_mode, NULL, RSRC_CONF|ACCESS_CONF, + "tradeoff performance vs security (fast or secure)"), +#ifdef BIG_SECURITY_HOLE + AP_INIT_ITERATE("VHostPrivs", vhost_privs, NULL, RSRC_CONF, + "Privileges available in the (virtual) server"), + AP_INIT_ITERATE("VHostCGIPrivs", vhost_cgiprivs, NULL, RSRC_CONF, + "Privileges available to external programs"), +#endif + {NULL} +}; +AP_DECLARE_MODULE(privileges) = { + STANDARD20_MODULE_STUFF, + privileges_create_dir_cfg, + privileges_merge_dir_cfg, + privileges_create_cfg, + privileges_merge_cfg, + privileges_cmds, + privileges_hooks +}; diff --git a/modules/arch/unix/mod_unixd.c b/modules/arch/unix/mod_unixd.c new file mode 100644 index 0000000..1baa278 --- /dev/null +++ b/modules/arch/unix/mod_unixd.c @@ -0,0 +1,433 @@ +/* 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. + */ + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_main.h" +#include "http_log.h" +#include "http_core.h" +#include "mpm_common.h" +#include "os.h" +#include "ap_mpm.h" +#include "mod_unixd.h" +#include "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_portable.h" +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif +/* XXX */ +#include <sys/stat.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#ifdef HAVE_SYS_SEM_H +#include <sys/sem.h> +#endif +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifndef DEFAULT_USER +#define DEFAULT_USER "#-1" +#endif +#ifndef DEFAULT_GROUP +#define DEFAULT_GROUP "#-1" +#endif + +#if 0 +typedef struct { + const char *user_name; + uid_t user_id; + gid_t group_id; + const char *chroot_dir; +} unixd_config_t; +#else +/* + * TODO: clean up the separation between this code + * and its data structures and unixd.c, as shown + * by the fact that we include unixd.h. Create + * mod_unixd.h which does what we need and + * clean up unixd.h for what it no longer needs + */ +#include "unixd.h" +#endif + + +/* Set group privileges. + * + * Note that we use the username as set in the config files, rather than + * the lookup of to uid --- the same uid may have multiple passwd entries, + * with different sets of groups for each. + */ + +static int set_group_privs(void) +{ + if (!geteuid()) { + const char *name; + + /* Get username if passed as a uid */ + + if (ap_unixd_config.user_name[0] == '#') { + struct passwd *ent; + uid_t uid = atol(&ap_unixd_config.user_name[1]); + + if ((ent = getpwuid(uid)) == NULL) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02155) + "getpwuid: couldn't determine user name from uid %ld, " + "you probably need to modify the User directive", + (long)uid); + return -1; + } + + name = ent->pw_name; + } + else + name = ap_unixd_config.user_name; + +#if !defined(OS2) + /* OS/2 doesn't support groups. */ + /* + * Set the GID before initgroups(), since on some platforms + * setgid() is known to zap the group list. + */ + if (setgid(ap_unixd_config.group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02156) + "setgid: unable to set group id to Group %ld", + (long)ap_unixd_config.group_id); + return -1; + } + + /* Reset `groups' attributes. */ + + if (initgroups(name, ap_unixd_config.group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02157) + "initgroups: unable to set groups for User %s " + "and Group %ld", name, (long)ap_unixd_config.group_id); + return -1; + } +#endif /* !defined(OS2) */ + } + return 0; +} + + +static int +unixd_drop_privileges(apr_pool_t *pool, server_rec *s) +{ + int rv = set_group_privs(); + + if (rv) { + return rv; + } + + if (NULL != ap_unixd_config.chroot_dir) { + if (geteuid()) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02158) + "Cannot chroot when not started as root"); + return rv; + } + + if (chdir(ap_unixd_config.chroot_dir) != 0) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02159) + "Can't chdir to %s", ap_unixd_config.chroot_dir); + return rv; + } + + if (chroot(ap_unixd_config.chroot_dir) != 0) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02160) + "Can't chroot to %s", ap_unixd_config.chroot_dir); + return rv; + } + + if (chdir("/") != 0) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02161) + "Can't chdir to new root"); + return rv; + } + } + + /* Only try to switch if we're running as root */ + if (!geteuid() && ( +#ifdef _OSD_POSIX + os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 || +#endif + setuid(ap_unixd_config.user_id) == -1)) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02162) + "setuid: unable to change to uid: %ld", + (long) ap_unixd_config.user_id); + return rv; + } +#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) + /* this applies to Linux 2.4+ */ + if (ap_coredumpdir_configured) { + if (prctl(PR_SET_DUMPABLE, 1)) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02163) + "set dumpable failed - this child will not coredump" + " after software errors"); + return rv; + } + } +#endif + + return OK; +} + + +static const char * +unixd_set_user(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_unixd_config.user_name = arg; + ap_unixd_config.user_id = ap_uname2id(arg); +#if !defined (BIG_SECURITY_HOLE) && !defined (OS2) + if (ap_unixd_config.user_id == 0) { + return "Error:\tApache has not been designed to serve pages while\n" + "\trunning as root. There are known race conditions that\n" + "\twill allow any local user to read any file on the system.\n" + "\tIf you still desire to serve pages as root then\n" + "\tadd -DBIG_SECURITY_HOLE to the CFLAGS env variable\n" + "\tand then rebuild the server.\n" + "\tIt is strongly suggested that you instead modify the User\n" + "\tdirective in your httpd.conf file to list a non-root\n" + "\tuser.\n"; + } +#endif + + return NULL; +} + +static const char* +unixd_set_group(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_unixd_config.group_name = arg; + ap_unixd_config.group_id = ap_gname2id(arg); + + return NULL; +} + +static const char* +unixd_set_chroot_dir(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!ap_is_directory(cmd->pool, arg)) { + return "ChrootDir must be a valid directory"; + } + + ap_unixd_config.chroot_dir = arg; + return NULL; +} + +static const char * +unixd_set_suexec(cmd_parms *cmd, void *dummy, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if (!ap_unixd_config.suexec_enabled && arg) { + return apr_pstrcat(cmd->pool, "suEXEC isn't supported: ", + ap_unixd_config.suexec_disabled_reason, NULL); + } + + if (!arg) { + ap_unixd_config.suexec_disabled_reason = "Suexec directive is Off"; + } + + ap_unixd_config.suexec_enabled = arg; + return NULL; +} + +#ifdef AP_SUEXEC_CAPABILITIES +/* If suexec is using capabilities, don't test for the setuid bit. */ +#define SETUID_TEST(finfo) (1) +#else +#define SETUID_TEST(finfo) (finfo.protection & APR_USETID) +#endif + +static int +unixd_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + apr_finfo_t wrapper; + ap_unixd_config.user_name = DEFAULT_USER; + ap_unixd_config.user_id = ap_uname2id(DEFAULT_USER); + ap_unixd_config.group_name = DEFAULT_GROUP; + ap_unixd_config.group_id = ap_gname2id(DEFAULT_GROUP); + + ap_unixd_config.chroot_dir = NULL; /* none */ + + /* Check for suexec */ + ap_unixd_config.suexec_enabled = 0; + if ((apr_stat(&wrapper, SUEXEC_BIN, APR_FINFO_NORM, ptemp)) + == APR_SUCCESS) { + if (SETUID_TEST(wrapper) && wrapper.user == 0 + && (access(SUEXEC_BIN, R_OK|X_OK) == 0)) { + ap_unixd_config.suexec_enabled = 1; + ap_unixd_config.suexec_disabled_reason = ""; + } + else { + ap_unixd_config.suexec_disabled_reason = + "Invalid owner or file mode for " SUEXEC_BIN; + } + } + else { + ap_unixd_config.suexec_disabled_reason = + "Missing suexec binary " SUEXEC_BIN; + } + + ap_sys_privileges_handlers(1); + return OK; +} + +AP_DECLARE(int) ap_unixd_setup_child(void) +{ + if (set_group_privs()) { + return -1; + } + + if (NULL != ap_unixd_config.chroot_dir) { + if (geteuid()) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02164) + "Cannot chroot when not started as root"); + return -1; + } + if (chdir(ap_unixd_config.chroot_dir) != 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02165) + "Can't chdir to %s", ap_unixd_config.chroot_dir); + return -1; + } + if (chroot(ap_unixd_config.chroot_dir) != 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02166) + "Can't chroot to %s", ap_unixd_config.chroot_dir); + return -1; + } + if (chdir("/") != 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02167) + "Can't chdir to new root"); + return -1; + } + } + + /* Only try to switch if we're running as root */ + if (!geteuid() && ( +#ifdef _OSD_POSIX + os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 || +#endif + setuid(ap_unixd_config.user_id) == -1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02168) + "setuid: unable to change to uid: %ld", + (long) ap_unixd_config.user_id); + return -1; + } +#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) + /* this applies to Linux 2.4+ */ + if (ap_coredumpdir_configured) { + if (prctl(PR_SET_DUMPABLE, 1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02169) + "set dumpable failed - this child will not coredump" + " after software errors"); + } + } +#endif + return 0; +} + +static void unixd_dump_config(apr_pool_t *p, server_rec *s) +{ + apr_file_t *out = NULL; + apr_uid_t uid = ap_unixd_config.user_id; + apr_gid_t gid = ap_unixd_config.group_id; + char *no_root = ""; + if (!ap_exists_config_define("DUMP_RUN_CFG")) + return; + if (geteuid() != 0) + no_root = " not_used"; + apr_file_open_stdout(&out, p); + apr_file_printf(out, "User: name=\"%s\" id=%lu%s\n", + ap_unixd_config.user_name, (unsigned long)uid, no_root); + apr_file_printf(out, "Group: name=\"%s\" id=%lu%s\n", + ap_unixd_config.group_name, (unsigned long)gid, no_root); + if (ap_unixd_config.chroot_dir) + apr_file_printf(out, "ChrootDir: \"%s\"%s\n", + ap_unixd_config.chroot_dir, no_root); +} + +static void unixd_hooks(apr_pool_t *pool) +{ + ap_hook_pre_config(unixd_pre_config, + NULL, NULL, APR_HOOK_FIRST); + ap_hook_test_config(unixd_dump_config, + NULL, NULL, APR_HOOK_FIRST); + ap_hook_drop_privileges(unixd_drop_privileges, + NULL, NULL, APR_HOOK_MIDDLE); +} + +static const command_rec unixd_cmds[] = { + AP_INIT_TAKE1("User", unixd_set_user, NULL, RSRC_CONF, + "Effective user id for this server"), + AP_INIT_TAKE1("Group", unixd_set_group, NULL, RSRC_CONF, + "Effective group id for this server"), + AP_INIT_TAKE1("ChrootDir", unixd_set_chroot_dir, NULL, RSRC_CONF, + "The directory to chroot(2) into"), + AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF, + "Enable or disable suEXEC support"), + {NULL} +}; + +AP_DECLARE_MODULE(unixd) = { + STANDARD20_MODULE_STUFF, + NULL, + NULL, + NULL, + NULL, + unixd_cmds, + unixd_hooks +}; + diff --git a/modules/arch/unix/mod_unixd.h b/modules/arch/unix/mod_unixd.h new file mode 100644 index 0000000..a7f439e --- /dev/null +++ b/modules/arch/unix/mod_unixd.h @@ -0,0 +1,41 @@ +/* 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. + */ + +/** + * @file mod_unixd.h + * @brief common stuff that unix MPMs will want + * + * @addtogroup APACHE_OS_UNIX + * @{ + */ + +#ifndef MOD_UNIXD_H +#define MOD_UNIXD_H + +#include "ap_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +AP_DECLARE(int) ap_unixd_setup_child(void); + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ |