2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 10:07:12 +00:00
Steve Beattie 6fd2f36bd8 mod_apparmor: add logging for AAHatName/AADefaultHatName policy misconfig
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>
2014-01-23 14:42:00 -08:00

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 */
};