mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-22 10:07:12 +00:00
This patch adds code that checks the resulting hat that apache gets placed into, and verifies that if the apache configuration specified that an AAHatName or AADefaultHatName should have been the resulting hat. If it wasn't, emit a warning message to the apache log, as this likely indicates a mismatch between the apache configuration and its apparmor policy (i.e. why define AAHatName if you aren't going to create the corresponding hat in the apparmor policy?) Note for AADefaultHatName, a message is not logged if a defined AAHatName would also apply or if there is a hat defined for the uri, as each of those come first in the order of attempted hats. Also note that the way the hat name is manually calculated will break for nested profiles and stacking. It should be fine for all current deployments as we don't allow nesting beyond the first subprofile level in policy yet. And stacking will likely only be used between namespaces where aa_getcon() will not report parent namespace info. However, when libapparmor adds functionality to query the hatname, the code that computes it here should be replaced by a call to that library function. Signed-off-by: Steve Beattie <steve@nxnw.org> Acked-by: John Johansen <john.johansen@canonical.com>
410 lines
12 KiB
C
410 lines
12 KiB
C
/*
|
|
* Copyright (c) 2004, 2005, 2006 NOVELL (All rights reserved)
|
|
*
|
|
* The mod_apparmor module is licensed under the terms of the GNU
|
|
* Lesser General Public License, version 2.1. Please see the file
|
|
* COPYING.LGPL.
|
|
*
|
|
* mod_apparmor - (apache 2.0.x)
|
|
* Author: Steve Beattie <sbeattie@suse.de>
|
|
*
|
|
* This currently only implements change_hat functionality, but could be
|
|
* extended for other stuff we decide to do.
|
|
*/
|
|
|
|
#include "ap_config.h"
|
|
#include "httpd.h"
|
|
#include "http_config.h"
|
|
#include "http_request.h"
|
|
#include "http_log.h"
|
|
#include "http_main.h"
|
|
#include "http_protocol.h"
|
|
#include "util_filter.h"
|
|
#include "apr.h"
|
|
#include "apr_strings.h"
|
|
#include "apr_lib.h"
|
|
|
|
#include <sys/apparmor.h>
|
|
#include <unistd.h>
|
|
|
|
/* #define DEBUG */
|
|
#ifndef __unused
|
|
#define __unused __attribute__((unused))
|
|
#endif
|
|
|
|
/* should the following be configurable? */
|
|
#define DEFAULT_HAT "HANDLING_UNTRUSTED_INPUT"
|
|
#define DEFAULT_URI_HAT "DEFAULT_URI"
|
|
|
|
/* Compatibility with apache 2.2 */
|
|
#if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER < 3
|
|
#define APLOG_TRACE1 APLOG_DEBUG
|
|
server_rec *ap_server_conf = NULL;
|
|
#endif
|
|
|
|
#ifdef APLOG_USE_MODULE
|
|
APLOG_USE_MODULE(apparmor);
|
|
#endif
|
|
module AP_MODULE_DECLARE_DATA apparmor_module;
|
|
|
|
static unsigned long magic_token = 0;
|
|
static int inside_default_hat = 0;
|
|
|
|
typedef struct {
|
|
const char * hat_name;
|
|
char * path;
|
|
} immunix_dir_cfg;
|
|
|
|
typedef struct {
|
|
const char * hat_name;
|
|
int is_initialized;
|
|
} immunix_srv_cfg;
|
|
|
|
/* immunix_init() gets invoked in the post_config stage of apache.
|
|
* Unfortunately, apache reads its config once when it starts up, then
|
|
* it re-reads it when goes into its restart loop, where it starts it's
|
|
* children. This means we cannot call change_hat here, as the modules
|
|
* memory will be wiped out, and the magic_token will be lost, so apache
|
|
* wouldn't be able to change_hat back out. */
|
|
static int
|
|
immunix_init (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
|
|
{
|
|
apr_file_t * file;
|
|
apr_size_t size = sizeof (magic_token);
|
|
int ret;
|
|
|
|
ret = apr_file_open (&file, "/dev/urandom", APR_READ, APR_OS_DEFAULT, p);
|
|
if (!ret) {
|
|
apr_file_read (file, (void *) &magic_token, &size);
|
|
apr_file_close (file);
|
|
} else {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to open /dev/urandom");
|
|
}
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "Opened /dev/urandom successfully");
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* As each child starts up, we'll change_hat into a default hat, mostly
|
|
* to protect ourselves from bugs in parsing network input, but before
|
|
* we change_hat to the uri specific hat. */
|
|
static void
|
|
immunix_child_init (apr_pool_t *p, server_rec *s)
|
|
{
|
|
int ret;
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf,
|
|
"init: calling change_hat with '%s'", DEFAULT_HAT);
|
|
ret = aa_change_hat(DEFAULT_HAT, magic_token);
|
|
if (ret < 0) {
|
|
aa_change_hat(NULL, magic_token);
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to change_hat to '%s'",
|
|
DEFAULT_HAT);
|
|
} else {
|
|
inside_default_hat = 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
debug_dump_uri(request_rec *r)
|
|
{
|
|
apr_uri_t *uri = &r->parsed_uri;
|
|
if (uri)
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Dumping uri info "
|
|
"scheme='%s' host='%s' path='%s' query='%s' fragment='%s'",
|
|
uri->scheme, uri->hostname, uri->path, uri->query,
|
|
uri->fragment);
|
|
else
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Asked to dump NULL uri");
|
|
|
|
}
|
|
|
|
/*
|
|
immunix_enter_hat will attempt to change_hat in the following order:
|
|
(1) to a hatname in a location directive
|
|
(2) to the uri
|
|
(3) to a per-server default
|
|
(4) to DEFAULT_URI
|
|
(5) back to the parent profile
|
|
*/
|
|
static int
|
|
immunix_enter_hat (request_rec *r)
|
|
{
|
|
int sd_ret = -1;
|
|
immunix_dir_cfg * dcfg = (immunix_dir_cfg *)
|
|
ap_get_module_config (r->per_dir_config, &apparmor_module);
|
|
immunix_srv_cfg * scfg = (immunix_srv_cfg *)
|
|
ap_get_module_config (r->server->module_config, &apparmor_module);
|
|
const char *aa_hat_array[5] = { NULL, NULL, NULL, NULL, NULL };
|
|
int i = 0;
|
|
char *aa_con, *aa_mode, *aa_hat;
|
|
|
|
debug_dump_uri(r);
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx",
|
|
dcfg->path, (unsigned long) r->next, (unsigned long) r->prev,
|
|
(unsigned long) r->main);
|
|
|
|
/* We only call change_hat for the main request, not subrequests */
|
|
if (r->main)
|
|
return OK;
|
|
|
|
if (inside_default_hat) {
|
|
aa_change_hat(NULL, magic_token);
|
|
inside_default_hat = 0;
|
|
}
|
|
|
|
if (dcfg != NULL && dcfg->hat_name != NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"[dcfg] adding hat '%s' to aa_change_hat vector", dcfg->hat_name);
|
|
aa_hat_array[i++] = dcfg->hat_name;
|
|
}
|
|
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"[uri] adding uri '%s' to aa_change_hat vector", r->uri);
|
|
aa_hat_array[i++] = r->uri;
|
|
|
|
if (scfg) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Dumping scfg info: "
|
|
"scfg='0x%lx' scfg->hat_name='%s'",
|
|
(unsigned long) scfg, scfg->hat_name);
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "scfg is null");
|
|
}
|
|
if (scfg != NULL) {
|
|
if (scfg->hat_name != NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"[scfg] adding hat '%s' to aa_change_hat vector", scfg->hat_name);
|
|
aa_hat_array[i++] = scfg->hat_name;
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"[scfg] adding server_name '%s' to aa_change_hat vector",
|
|
r->server->server_hostname);
|
|
aa_hat_array[i++] = r->server->server_hostname;
|
|
}
|
|
}
|
|
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"[default] adding '%s' to aa_change_hat vector", DEFAULT_URI_HAT);
|
|
aa_hat_array[i++] = DEFAULT_URI_HAT;
|
|
|
|
sd_ret = aa_change_hatv(aa_hat_array, magic_token);
|
|
if (sd_ret < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, r, "aa_change_hatv call failed");
|
|
}
|
|
|
|
/* Check to see if a defined AAHatName or AADefaultHatName would
|
|
* apply, but wasn't the hat we landed up in; report a warning if
|
|
* that's the case. */
|
|
sd_ret = aa_getcon(&aa_con, &aa_mode);
|
|
if (sd_ret < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, r, "aa_getcon call failed");
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
|
|
"AA checks: aa_getcon result is '%s', mode '%s'", aa_con, aa_mode);
|
|
/* TODO: use libapparmor get hat_name fn here once it is implemented */
|
|
aa_hat = strstr(aa_con, "//");
|
|
if (aa_hat != NULL && strcmp(aa_mode, "enforce") == 0) {
|
|
aa_hat += 2; /* skip "//" */
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
|
|
"AA checks: apache is in hat '%s', mode '%s'", aa_hat, aa_mode);
|
|
if (dcfg != NULL && dcfg->hat_name != NULL) {
|
|
if (strcmp(aa_hat, dcfg->hat_name) != 0)
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"AAHatName '%s' applies, but does not appear to be a hat in the apache apparmor policy",
|
|
dcfg->hat_name);
|
|
} else if (scfg != NULL && scfg->hat_name != NULL) {
|
|
if (strcmp(aa_hat, scfg->hat_name) != 0 &&
|
|
strcmp(aa_hat, r->uri) != 0)
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"AADefaultHatName '%s' applies, but does not appear to be a hat in the apache apparmor policy",
|
|
scfg->hat_name);
|
|
}
|
|
}
|
|
free(aa_con);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
immunix_exit_hat (request_rec *r)
|
|
{
|
|
int sd_ret;
|
|
immunix_dir_cfg * dcfg = (immunix_dir_cfg *)
|
|
ap_get_module_config (r->per_dir_config, &apparmor_module);
|
|
/* immunix_srv_cfg * scfg = (immunix_srv_cfg *)
|
|
ap_get_module_config (r->server->module_config, &apparmor_module); */
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "exiting change_hat: dir hat %s dir path %s",
|
|
dcfg->hat_name, dcfg->path);
|
|
|
|
/* can convert the following back to aa_change_hat() when the
|
|
* aa_change_hat() bug addressed in trunk commit 2329 lands in most
|
|
* system libapparmors */
|
|
aa_change_hatv(NULL, magic_token);
|
|
|
|
sd_ret = aa_change_hat(DEFAULT_HAT, magic_token);
|
|
if (sd_ret < 0) {
|
|
aa_change_hat(NULL, magic_token);
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to change_hat to '%s'",
|
|
DEFAULT_HAT);
|
|
} else {
|
|
inside_default_hat = 1;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static const char *
|
|
aa_cmd_ch_path (cmd_parms * cmd, void * mconfig, const char * parm1)
|
|
{
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "directory config change hat %s",
|
|
parm1 ? parm1 : "DEFAULT");
|
|
immunix_dir_cfg * dcfg = mconfig;
|
|
if (parm1 != NULL) {
|
|
dcfg->hat_name = parm1;
|
|
} else {
|
|
dcfg->hat_name = "DEFAULT";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int path_warn_once;
|
|
|
|
static const char *
|
|
immunix_cmd_ch_path (cmd_parms * cmd, void * mconfig, const char * parm1)
|
|
{
|
|
if (path_warn_once == 0) {
|
|
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, "ImmHatName is "
|
|
"deprecated, please use AAHatName instead");
|
|
path_warn_once = 1;
|
|
}
|
|
return aa_cmd_ch_path(cmd, mconfig, parm1);
|
|
}
|
|
|
|
static const char *
|
|
aa_cmd_ch_srv (cmd_parms * cmd, void * mconfig, const char * parm1)
|
|
{
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "server config change hat %s",
|
|
parm1 ? parm1 : "DEFAULT");
|
|
immunix_srv_cfg * scfg = (immunix_srv_cfg *)
|
|
ap_get_module_config(cmd->server->module_config, &apparmor_module);
|
|
if (parm1 != NULL) {
|
|
scfg->hat_name = parm1;
|
|
} else {
|
|
scfg->hat_name = "DEFAULT";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int srv_warn_once;
|
|
|
|
static const char *
|
|
immunix_cmd_ch_srv (cmd_parms * cmd, void * mconfig, const char * parm1)
|
|
{
|
|
if (srv_warn_once == 0) {
|
|
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, "ImmDefaultHatName is "
|
|
"deprecated, please use AADefaultHatName instead");
|
|
srv_warn_once = 1;
|
|
}
|
|
return aa_cmd_ch_srv(cmd, mconfig, parm1);
|
|
}
|
|
|
|
static void *
|
|
immunix_create_dir_config (apr_pool_t * p, char * path)
|
|
{
|
|
immunix_dir_cfg * newcfg = (immunix_dir_cfg *) apr_pcalloc(p, sizeof(* newcfg));
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "in immunix_create_dir (%s)", path ? path : ":no path:");
|
|
if (newcfg == NULL) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "immunix_create_dir: couldn't alloc dir config");
|
|
return NULL;
|
|
}
|
|
newcfg->path = apr_pstrdup (p, path ? path : ":no path:");
|
|
|
|
return newcfg;
|
|
}
|
|
|
|
/* XXX: Should figure out an appropriate action to take here, if any
|
|
|
|
static void *
|
|
immunix_merge_dir_config (apr_pool_t * p, void * parent, void * child)
|
|
{
|
|
immunix_dir_cfg * newcfg = (immunix_dir_cfg *) apr_pcalloc(p, sizeof(* newcfg));
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "in immunix_merge_dir ()");
|
|
if (newcfg == NULL)
|
|
return NULL;
|
|
|
|
return newcfg;
|
|
}
|
|
*/
|
|
|
|
static void *
|
|
immunix_create_srv_config (apr_pool_t * p, server_rec * srv)
|
|
{
|
|
immunix_srv_cfg * newcfg = (immunix_srv_cfg *) apr_pcalloc(p, sizeof(* newcfg));
|
|
|
|
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "in immunix_create_srv");
|
|
if (newcfg == NULL) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "immunix_create_srv: couldn't alloc srv config");
|
|
return NULL;
|
|
}
|
|
|
|
return newcfg;
|
|
}
|
|
|
|
|
|
static const command_rec immunix_cmds[] = {
|
|
|
|
AP_INIT_TAKE1 (
|
|
"ImmHatName",
|
|
immunix_cmd_ch_path,
|
|
NULL,
|
|
ACCESS_CONF,
|
|
""
|
|
),
|
|
AP_INIT_TAKE1 (
|
|
"ImmDefaultHatName",
|
|
immunix_cmd_ch_srv,
|
|
NULL,
|
|
RSRC_CONF,
|
|
""
|
|
),
|
|
AP_INIT_TAKE1 (
|
|
"AAHatName",
|
|
aa_cmd_ch_path,
|
|
NULL,
|
|
ACCESS_CONF,
|
|
""
|
|
),
|
|
AP_INIT_TAKE1 (
|
|
"AADefaultHatName",
|
|
aa_cmd_ch_srv,
|
|
NULL,
|
|
RSRC_CONF,
|
|
""
|
|
),
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
register_hooks (apr_pool_t *p)
|
|
{
|
|
ap_hook_post_config (immunix_init, NULL, NULL, APR_HOOK_MIDDLE);
|
|
ap_hook_child_init (immunix_child_init, NULL, NULL, APR_HOOK_MIDDLE);
|
|
ap_hook_access_checker(immunix_enter_hat, NULL, NULL, APR_HOOK_FIRST);
|
|
/* ap_hook_post_read_request(immunix_enter_hat, NULL, NULL, APR_HOOK_FIRST); */
|
|
ap_hook_log_transaction(immunix_exit_hat, NULL, NULL, APR_HOOK_LAST);
|
|
}
|
|
|
|
module AP_MODULE_DECLARE_DATA apparmor_module = {
|
|
STANDARD20_MODULE_STUFF,
|
|
immunix_create_dir_config, /* dir config creater */
|
|
NULL, /* dir merger --- default is to override */
|
|
/* immunix_merge_dir_config, */ /* dir merger --- default is to override */
|
|
immunix_create_srv_config, /* server config */
|
|
NULL, /* merge server config */
|
|
immunix_cmds, /* command table */
|
|
register_hooks /* register hooks */
|
|
};
|