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 <kai...@ak.jp.nec.com>
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 <stdio.h>
+#include <string.h>
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+
+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

Reply via email to