Return-Path: Delivered-To: apmail-httpd-dev-archive@www.apache.org Received: (qmail 7839 invoked from network); 8 Apr 2009 01:39:23 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 8 Apr 2009 01:39:23 -0000 Received: (qmail 96544 invoked by uid 500); 8 Apr 2009 01:39:22 -0000 Delivered-To: apmail-httpd-dev-archive@httpd.apache.org Received: (qmail 96467 invoked by uid 500); 8 Apr 2009 01:39:22 -0000 Mailing-List: contact dev-help@httpd.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@httpd.apache.org list-help: list-unsubscribe: List-Post: List-Id: Delivered-To: mailing list dev@httpd.apache.org Received: (qmail 96458 invoked by uid 99); 8 Apr 2009 01:39:22 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 08 Apr 2009 01:39:22 +0000 X-ASF-Spam-Status: No, hits=-0.0 required=10.0 tests=SPF_HELO_PASS,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (athena.apache.org: domain of kaigai@ak.jp.nec.com designates 202.32.8.206 as permitted sender) Received: from [202.32.8.206] (HELO tyo202.gate.nec.co.jp) (202.32.8.206) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 08 Apr 2009 01:39:15 +0000 Received: from mailgate3.nec.co.jp ([10.7.69.162]) by tyo202.gate.nec.co.jp (8.13.8/8.13.4) with ESMTP id n381crDB012162 for ; Wed, 8 Apr 2009 10:38:53 +0900 (JST) Received: (from root@localhost) by mailgate3.nec.co.jp (8.11.7/3.7W-MAILGATE-NEC) id n381crE02736 for dev@httpd.apache.org; Wed, 8 Apr 2009 10:38:53 +0900 (JST) Received: from mailsv.linux.bs1.fc.nec.co.jp (mailsv.linux.bs1.fc.nec.co.jp [10.34.125.2]) by mailsv.nec.co.jp (8.13.8/8.13.4) with ESMTP id n381cq0U011796 for ; Wed, 8 Apr 2009 10:38:52 +0900 (JST) Received: from [10.19.71.82] (unknown [10.19.71.82]) by mailsv.linux.bs1.fc.nec.co.jp (Postfix) with ESMTP id 845F7E482A3 for ; Wed, 8 Apr 2009 10:38:52 +0900 (JST) Message-ID: <49DC002C.8080600@ak.jp.nec.com> Date: Wed, 08 Apr 2009 10:38:52 +0900 From: KaiGai Kohei User-Agent: Thunderbird 2.0.0.6 (Windows/20070728) MIME-Version: 1.0 To: dev@httpd.apache.org Subject: [RFC] A new hook: invoke_handler and web-application security Content-Type: multipart/mixed; boundary="------------030401000803010802020801" X-Virus-Checked: Checked by ClamAV on apache.org This is a multi-part message in MIME format. --------------030401000803010802020801 Content-Type: text/plain; charset=ISO-2022-JP Content-Transfer-Encoding: 7bit Hello, I've posted my idea to improve web-application security a few times however, it could not interest folks unfortunatelly. :( So, I would like to offer another approach for the purpose. The attached patch is a proof of the concept of newer idea. Any comments are welcome, and please feel free. The attached patch adds the following hook: AP_DECLARE_HOOK(int,invoke_handler,(request_rec *r)) The server/core.c registers the ap_invoke_handler() as a default fallback, and all the ap_invoke_handler() invocations are replaced by ap_run_invoke_handler(), so we don't have any compatibility issue as far as no modules uses the new hooks. The purpose of this new hooks is to give modules a chance to assign an appropriate privilege set before contents handler launched. The mod_selinux.c is a typical example. It acquires a control via the invoke_handler hook whenever someone tries to invoke contents handler, then it compute what privilege (called as security context) should be assigned during the contents handler execution. If the computed privilege is same as the current one, it just returns DECLINES. But, if the computed one is different from the current, it creates a one-time worker thread and wait for its completion. The worker thread set a new privilege on itself and invokes ap_invoke_handler() with restricted privilege. In the previous design proposal, I added hooks just before ap_process_(async_)request(), but I noticed it cannot handle a case of internal redirection. BTW, Please note that the purpose of our efforts is to launch web applications with individual privilege set, not to add new hooks. Now I think the idea is the shortest distance to the goal, but is there any other ideas? If you have anything, I would like to see it. Thanks, -- OSS Platform Development Division, NEC KaiGai Kohei --------------030401000803010802020801 Content-Type: text/x-patch; name="apache-httpd-selinux-support.2.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="apache-httpd-selinux-support.2.patch" Index: server/config.c =================================================================== --- server/config.c (revision 763027) +++ server/config.c (working copy) @@ -68,6 +68,7 @@ APR_HOOK_LINK(post_config) APR_HOOK_LINK(open_logs) APR_HOOK_LINK(child_init) + APR_HOOK_LINK(invoke_handler) APR_HOOK_LINK(handler) APR_HOOK_LINK(quick_handler) APR_HOOK_LINK(optional_fn_retrieve) @@ -157,6 +158,9 @@ (apr_pool_t *pchild, server_rec *s), (pchild, s)) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, invoke_handler, (request_rec *r), + (r), DECLINED) + AP_IMPLEMENT_HOOK_RUN_FIRST(int, handler, (request_rec *r), (r), DECLINED) Index: server/core.c =================================================================== --- server/core.c (revision 763027) +++ server/core.c (working copy) @@ -3917,6 +3917,7 @@ ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST); ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE); + ap_hook_invoke_handler(ap_invoke_handler,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST); /* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */ ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST); Index: server/request.c =================================================================== --- server/request.c (revision 763027) +++ server/request.c (working copy) @@ -2066,7 +2066,7 @@ retval = ap_run_quick_handler(r, 0); } if (retval != OK) { - retval = ap_invoke_handler(r); + retval = ap_run_invoke_handler(r); if (retval == DONE) { retval = OK; } Index: modules/http/http_request.c =================================================================== --- modules/http/http_request.c (revision 763027) +++ modules/http/http_request.c (working copy) @@ -298,7 +298,7 @@ if (access_status == DECLINED) { access_status = ap_process_request_internal(r); if (access_status == OK) { - access_status = ap_invoke_handler(r); + access_status = ap_run_invoke_handler(r); } } @@ -576,7 +576,7 @@ if (access_status == DECLINED) { access_status = ap_process_request_internal(new); if (access_status == OK) { - access_status = ap_invoke_handler(new); + access_status = ap_run_invoke_handler(new); } } if (access_status == OK) { @@ -605,7 +605,7 @@ ap_set_content_type(new, r->content_type); access_status = ap_process_request_internal(new); if (access_status == OK) { - if ((access_status = ap_invoke_handler(new)) != 0) { + if ((access_status = ap_run_invoke_handler(new)) != 0) { ap_die(access_status, new); return; } Index: modules/arch/unix/mod_selinux.c =================================================================== --- modules/arch/unix/mod_selinux.c (revision 0) +++ modules/arch/unix/mod_selinux.c (revision 0) @@ -0,0 +1,335 @@ +/* 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 "apr_strings.h" +#include "apr_thread_proc.h" + +#include "httpd.h" +#include "http_request.h" +#include "http_config.h" +#include "http_log.h" +#include +#include +#include +#include + +typedef struct +{ + char *dirname; + char *config_file; + char *default_domain; +} selinux_config; + +typedef struct +{ + request_rec *r; + security_context_t context; +} selinux_argument; + +module AP_MODULE_DECLARE_DATA selinux_module; + +static void * APR_THREAD_FUNC +selinux_invoke_worker(apr_thread_t *thd, void *datap) +{ + selinux_argument *sarg = (selinux_argument *) datap; + int retval; + + /* + * Set the given security context before ap_invoke_handler() + */ + if (setcon_raw(sarg->context) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, sarg->r->server, + "setcon_raw(%s) failed: %s", + sarg->context, strerror(errno)); + apr_thread_exit(thd, HTTP_INTERNAL_SERVER_ERROR); + } + + retval = ap_invoke_handler(sarg->r); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, retval, sarg->r->server, + "invokes handlers with: context=\"%s\" user=%s addr=%s", + sarg->context, + sarg->r->user ? sarg->r->user : "anonymous", + sarg->r->connection->remote_ip); + + apr_thread_exit(thd, retval); + return NULL; +} + +static int +selinux_invoke_internal(request_rec *r, security_context_t context) +{ + selinux_argument sarg; + apr_thread_t *thread; + apr_status_t rv, thread_rv; + + sarg.r = r; + sarg.context = context; + + /* + * Create one-time-thread and wait for its completion + */ + rv = apr_thread_create(&thread, + NULL, + selinux_invoke_worker, + &sarg, + r->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, + "SELinux: unable to create a worker thread"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + rv = apr_thread_join(&thread_rv, thread); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, + "SELinux: unable to join the worker thread"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + return thread_rv; +} + +#define WHITESPACE " \t\r\n" + +static char * +selinux_lookup_entry(request_rec *r, const char *filename) +{ + char buffer[1024], *ident, *entry, *mask, *tmp; + apr_ipsubnet_t *ipsub; + int negative, lineno = 0; + FILE *filp; + + filp = fopen(filename, "rb"); + if (!filp) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "SELinux: unable to open %s (%s)", + filename, strerror(errno)); + return NULL; + } + + while (fgets(buffer, sizeof(buffer), filp)) { + negative = 0; + lineno++; + + tmp = strchr(buffer, '#'); + if (tmp) + *tmp = '\0'; + + ident = strtok_r(buffer, WHITESPACE, &tmp); + /* skip, if empty */ + if (!ident) + continue; + + /* check negative condition */ + if (*ident == '!') { + ident++; + negative = 1; + } + + /* fetch domain and range */ + entry = strtok_r(NULL, WHITESPACE, &tmp); + if (!entry || strtok_r(NULL, WHITESPACE, &tmp)) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server, + "SELinux: syntax error at %s:%d", + filename, lineno); + continue; + } + + /* ident is network address? */ + mask = strchr(ident, '/'); + if (mask) + *mask++ = '\0'; + + if (apr_ipsubnet_create(&ipsub, ident, mask, r->pool) == APR_SUCCESS) { + if (apr_ipsubnet_test(ipsub, r->connection->remote_addr)) { + if (!negative) + goto match; + } else if (negative) + goto match; + } + else if (r->user) { + if (mask) + *--mask = '/'; /* fixup assumption of network address */ + if (strcmp(r->user, ident) == 0) { + if (!negative) + goto match; + } else if (negative) + goto match; + } + } + fclose(filp); + + return NULL; /* no matched entry */ + + match: + fclose(filp); + + return apr_pstrdup(r->pool, entry); +} + +static int +selinux_invoke_handler(request_rec *r) +{ + selinux_config *sconf; + security_context_t old_context; + security_context_t new_context; + context_t context; + int retval = DECLINED; + char *range, *domain = NULL; + + sconf = ap_get_module_config(r->per_dir_config, + &selinux_module); + if (!sconf) + return DECLINED; + + /* + * Is there any matched entry or default domain + * configured? If not, this module does not anything. + */ + if (sconf->config_file) + domain = selinux_lookup_entry(r, sconf->config_file); + if (!domain) + domain = apr_pstrdup(r->pool, sconf->default_domain); + if (!domain) + return DECLINED; /* no matched and default domain */ + + /* + * Compute a new security context + */ + range = strchr(domain, ':'); + if (range) + *range++ = '\0'; + if (strcmp(domain, "*") == 0) + domain = NULL; /* unchange */ + if (strcmp(range, "*") == 0) + range = NULL; /* unchange */ + + if (getcon_raw(&old_context) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "SELinux: getcon_raw() failed : %s", + strerror(errno)); + return HTTP_INTERNAL_SERVER_ERROR; + } + + context = context_new(old_context); + if (!context) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "SELinux: context_raw(%s) failed : %s", + old_context, strerror(errno)); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if (domain) + context_type_set(context, domain); + if (range) + context_range_set(context, range); + + new_context = context_str(context); + if (!new_context) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "SELinux: context_str() failed : %s", + strerror(errno)); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * If the configuration and authentication requires + * contents handler should work with different security + * context, we need to create a one-time-thread and + * assign the required security context before launch + * of contents handlers. + */ + if (strcmp(old_context, new_context) != 0) + retval = selinux_invoke_internal(r, new_context); + + freecon(old_context); + context_free(context); + + return retval; +} + +static void * +selinux_create_dir_config(apr_pool_t *p, char *dirname) +{ + selinux_config *sconf + = apr_palloc(p, sizeof(selinux_config)); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, + "SELinux: create dir config at: %s", dirname); + + sconf->dirname = apr_pstrdup(p, dirname); + sconf->config_file = NULL; + sconf->default_domain = NULL; + + return sconf; +} + +static const char * +set_config_file(cmd_parms *cmd, void *mconfig, const char *v1) +{ + selinux_config *sconf + = ap_get_module_config(cmd->context, &selinux_module); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, + "selinuxUserMappingFile = %s at %s", + v1, sconf->dirname); + + sconf->config_file = apr_pstrdup(cmd->pool, v1); + + return NULL; +} + +static const char * +set_default_domain(cmd_parms *cmd, void *mconfig, const char *v1) +{ + selinux_config *sconf + = ap_get_module_config(cmd->context, &selinux_module); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, + "selinuxDefaultDomain = %s at %s", + v1, sconf->dirname); + + sconf->default_domain = apr_pstrdup(cmd->pool, v1); + + return NULL; +} + +static void selinux_register_hooks(apr_pool_t *p) +{ + ap_hook_invoke_handler(selinux_invoke_handler, + NULL, NULL, APR_HOOK_MIDDLE); +} + +static const command_rec selinux_cmds[] = { + AP_INIT_TAKE1("selinuxConfigFile", + set_config_file, NULL, OR_OPTIONS, + "SELinux user/domain mapping file"), + AP_INIT_TAKE1("selinuxDefaultDomain", + set_default_domain, NULL, OR_OPTIONS, + "SELinux default security context"), + {NULL}, +}; + +module AP_MODULE_DECLARE_DATA selinux_module = +{ + STANDARD20_MODULE_STUFF, + selinux_create_dir_config, /* create per-directory config */ + NULL, /* merge per-directory config */ + NULL, /* server config creator */ + NULL, /* server config merger */ + selinux_cmds, /* command table */ + selinux_register_hooks, /* set up other hooks */ +}; Index: modules/arch/unix/config5.m4 =================================================================== --- modules/arch/unix/config5.m4 (revision 763027) +++ modules/arch/unix/config5.m4 (working copy) @@ -18,5 +18,11 @@ fi ]) +APACHE_MODULE(selinux, SELinux based secure web application platform, , , no, [ + AC_CHECK_LIB(selinux, getcon, + APR_ADDTO(MOD_SELINUX_LDADD, [-lselinux]), + AC_MSG_ERROR([libselinux is not installed])) +]) + APACHE_MODPATH_FINISH Index: include/http_config.h =================================================================== --- include/http_config.h (revision 763027) +++ include/http_config.h (working copy) @@ -1042,6 +1042,16 @@ AP_DECLARE_HOOK(void,child_init,(apr_pool_t *pchild, server_rec *s)) /** + * This hook gives a chance modules to override the ap_invoke_handler. + * It enables to set appropriate privileges before invocations of + * contents handlers. + * + * @param r The request_rec + * @remark the core registers ap_invoke_handler as default. + */ +AP_DECLARE_HOOK(int,invoke_handler,(request_rec *r)) + +/** * Run the handler functions for each module * @param r The request_rec * @remark non-wildcard handlers should HOOK_MIDDLE, wildcard HOOK_LAST --------------030401000803010802020801--