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.


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@771144 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Kew
2009-05-03 23:02:35 +00:00
parent 9d5bc3d38f
commit 40361e60f3

View File

@ -38,13 +38,20 @@ 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;
@ -56,6 +63,15 @@ static apr_status_t priv_cfg_cleanup(void *CFG)
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 @@ static void *privileges_create_cfg(apr_pool_t *pool, server_rec *s)
/* 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 @@ static void *privileges_create_cfg(apr_pool_t *pool, server_rec *s)
}
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 @@ static apr_status_t privileges_end_req(void *data)
}
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 @@ static int privileges_req(request_rec *r)
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_postconf(apr_pool_t *pconf, apr_pool_t *plog,
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 @@ static int privileges_init(apr_pool_t *pconf, apr_pool_t *plog,
"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 @@ static const char *dtraceenable(cmd_parms *cmd, void *dir, int 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)
{
@ -394,18 +569,19 @@ static const char *vhost_cgiprivs(cmd_parms *cmd, void *dir, const char *arg)
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 @@ static const command_rec privileges_cmds[] = {
};
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
};