You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ni...@apache.org on 2009/05/04 01:02:36 UTC
svn commit: r771144 - /httpd/httpd/trunk/modules/arch/unix/mod_privileges.c
Author: niq
Date: Sun May 3 23:02:35 2009
New Revision: 771144
URL: http://svn.apache.org/viewvc?rev=771144&view=rev
Log:
mod_privileges: introduce PrivilegesMode: fast mode as before vs secure
mode to fork an unprivileged child per-request in the manner of MPM-ITK
anwhere there's a risk of running malicious code.
Documentation to follow.
Modified:
httpd/httpd/trunk/modules/arch/unix/mod_privileges.c
Modified: httpd/httpd/trunk/modules/arch/unix/mod_privileges.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/arch/unix/mod_privileges.c?rev=771144&r1=771143&r2=771144&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/arch/unix/mod_privileges.c (original)
+++ httpd/httpd/trunk/modules/arch/unix/mod_privileges.c Sun May 3 23:02:35 2009
@@ -38,13 +38,20 @@
/* #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;
@@ -56,6 +63,15 @@
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));
@@ -83,6 +99,7 @@
/* 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);
@@ -92,16 +109,41 @@
}
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))) {
+ // return APR_SUCCESS;
+ 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) {
@@ -125,15 +167,102 @@
}
return APR_SUCCESS;
}
+#if 0
+static apr_status_t privileges_end_proc(void *data)
+{
+ /* FIXME
+ * The process exists only for the request, and was created
+ * on the request pool which is now being destroyed.
+ * Need to figure out what needs doing here.
+ */
+ exit(0);
+}
+#endif
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,
+ "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, "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, "In child!");
+// apr_pool_cleanup_register(r->pool, r, privileges_end_proc,
+// apr_pool_cleanup_null);
+ break; /* now we'll drop privileges in the child */
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "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) {
@@ -173,6 +302,16 @@
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, 0, r,
+ "Error setting permitted privileges: %s",
+ strerror(errno));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
return OK;
}
#define PDROP_CHECK(x) if (x == -1) { \
@@ -261,6 +400,7 @@
static int privileges_init(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp)
{
+#if 0
/* refuse to work if the MPM is threaded */
int threaded;
int rv = ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded);
@@ -275,12 +415,14 @@
"mod_privileges is not compatible with a threaded MPM.");
return !OK;
}
+#endif
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);
@@ -357,6 +499,39 @@
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)
{
@@ -394,18 +569,19 @@
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 secure mode (default ON)"),
+ "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"),
@@ -416,10 +592,10 @@
};
module AP_MODULE_DECLARE_DATA privileges_module = {
STANDARD20_MODULE_STUFF,
- NULL,
- NULL,
+ privileges_create_dir_cfg,
+ privileges_merge_dir_cfg,
privileges_create_cfg,
- NULL,
+ privileges_merge_cfg,
privileges_cmds,
privileges_hooks
};