apparmor/parser/parser_interface.c
John Johansen e08eaa39e2 Fix profile loads from cache files that contain multiple profiles
v3: fix freeing of filename when undefined
v2: address tyhicks feedback
    refactor to have a common write routine
    fix issue with set profile load being done even if !kernel_load

Profile loads from cache files that contain multiple profiles can
result in multiple reloads of the same profile or error messages about
failure to load profiles if the --add option is used. eg.

  apparmor="STATUS" operation="profile_load"
  name="/usr/lib/apache2/mpm-prefork/apache2" pid=8631
  comm="apparmor_parser"
  <sth0R> [82932.058388] type=1400 audit(1395415826.937:616):
  apparmor="STATUS" operation="profile_load" name="DEFAULT_URI" pid=8631
  comm="apparmor_parser"
  <sth0R> [82932.058391] type=1400 audit(1395415826.937:617):
  apparmor="STATUS" operation="profile_load"
  name="HANDLING_UNTRUSTED_INPUT" pid=8631 comm="apparmor_parser"
  <sth0R> [82932.058394] type=1400 audit(1395415826.937:618):
  apparmor="STATUS" operation="profile_load" name="phpsysinfo" pid=8631
  comm="apparmor_parser"
  <sth0R> [82932.059058] type=1400 audit(1395415826.937:619):
  apparmor="STATUS" operation="profile_replace" info="profile can not be
  replaced" error=-17
  name="/usr/lib/apache2/mpm-prefork/apache2//DEFAULT_URI" pid=8631
  comm="apparmor_parser"
  <sth0R> [82932.059574] type=1400 audit(1395415826.937:620):
  apparmor="STATUS" operation="profile_replace" info="profile can not be
  replaced" error=-17
  name="/usr/lib/apache2/mpm-prefork/apache2//HANDLING_UNTRUSTED_INPUT"
  pid=8631 comm="apparmor_parser"


The reason this happens is that the cache file is a container that
can contain multiple profiles in sequential order
  profile1
  profile2
  profile3

The parser loads the entire cache file to memory and the writes the
whole file to the kernel interface. It then skips foward in the file
to the next profile and reloads the file from that profile into
the kernel.
  eg. First load
    profile1
    profile2
    profile3

  advance to profile2, do second load
    profile2
    profile3

  advance to profile3, do third load
    profile3


With older kernels the interface would stop after the first profile and
return that it had processed the whole file, thus while wasting compute
resources copying extra data no errors occurred. However newer kernels
now support atomic loading of multipe profiles, so that all the profiles
passed in to the interface get processed.

This means on newer kernels the current parser load behavior results
in multiple loads/replacements when a cache file contains more than
one profile (note: loads from a compile do not have this problem).

To fix this, detect if the kernel supports atomic set loads, and load
the cache file once. If it doesn't only load one profile section
from a cache file at a time.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2014-05-08 09:03:13 -07:00

706 lines
16 KiB
C

/*
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
*
* Copyright (c) 2013
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Novell, Inc.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#define _(s) gettext(s)
#include <string>
#include <sstream>
#include "parser.h"
#include "profile.h"
#include "libapparmor_re/apparmor_re.h"
#include <unistd.h>
#include <linux/unistd.h>
#include <libintl.h>
#define _(s) gettext(s)
#define SD_CODE_SIZE (sizeof(u8))
#define SD_STR_LEN (sizeof(u16))
int __sd_serialize_profile(int option, Profile *prof);
static void print_error(int error)
{
switch (error) {
case -ESPIPE:
PERROR(_("Bad write position\n"));
break;
case -EPERM:
PERROR(_("Permission denied\n"));
break;
case -ENOMEM:
PERROR(_("Out of memory\n"));
break;
case -EFAULT:
PERROR(_("Couldn't copy profile: Bad memory address\n"));
break;
case -EPROTO:
PERROR(_("Profile doesn't conform to protocol\n"));
break;
case -EBADMSG:
PERROR(_("Profile does not match signature\n"));
break;
case -EPROTONOSUPPORT:
PERROR(_("Profile version not supported by Apparmor module\n"));
break;
case -EEXIST:
PERROR(_("Profile already exists\n"));
break;
case -ENOENT:
PERROR(_("Profile doesn't exist\n"));
break;
case -EACCES:
PERROR(_("Permission denied; attempted to load a profile while confined?\n"));
break;
default:
PERROR(_("Unknown error (%d): %s\n"), -error, strerror(-error));
break;
}
}
int load_profile(int option, Profile *prof)
{
int retval = 0;
int error = 0;
PDEBUG("Serializing policy for %s.\n", prof->name);
retval = __sd_serialize_profile(option, prof);
if (retval < 0) {
error = retval; /* yeah, we'll just report the last error */
switch (option) {
case OPTION_ADD:
PERROR(_("%s: Unable to add \"%s\". "),
progname, prof->name);
print_error(error);
break;
case OPTION_REPLACE:
PERROR(_("%s: Unable to replace \"%s\". "),
progname, prof->name);
print_error(error);
break;
case OPTION_REMOVE:
PERROR(_("%s: Unable to remove \"%s\". "),
progname, prof->name);
print_error(error);
break;
case OPTION_STDOUT:
PERROR(_("%s: Unable to write to stdout\n"),
progname);
break;
case OPTION_OFILE:
PERROR(_("%s: Unable to write to output file\n"),
progname);
default:
PERROR(_("%s: ASSERT: Invalid option: %d\n"),
progname, option);
exit(1);
break;
}
} else if (conf_verbose) {
switch (option) {
case OPTION_ADD:
printf(_("Addition succeeded for \"%s\".\n"),
prof->name);
break;
case OPTION_REPLACE:
printf(_("Replacement succeeded for \"%s\".\n"),
prof->name);
break;
case OPTION_REMOVE:
printf(_("Removal succeeded for \"%s\".\n"),
prof->name);
break;
case OPTION_STDOUT:
case OPTION_OFILE:
break;
default:
PERROR(_("%s: ASSERT: Invalid option: %d\n"),
progname, option);
exit(1);
break;
}
}
return error;
}
enum sd_code {
SD_U8,
SD_U16,
SD_U32,
SD_U64,
SD_NAME, /* same as string except it is items name */
SD_STRING,
SD_BLOB,
SD_STRUCT,
SD_STRUCTEND,
SD_LIST,
SD_LISTEND,
SD_ARRAY,
SD_ARRAYEND,
SD_OFFSET
};
const char *sd_code_names[] = {
"SD_U8",
"SD_U16",
"SD_U32",
"SD_U64",
"SD_NAME",
"SD_STRING",
"SD_BLOB",
"SD_STRUCT",
"SD_STRUCTEND",
"SD_LIST",
"SD_LISTEND",
"SD_ARRAY",
"SD_ARRAYEND",
"SD_OFFSET"
};
inline void sd_write8(std::ostringstream &buf, u8 b)
{
buf.write((const char *) &b, 1);
}
inline void sd_write16(std::ostringstream &buf, u16 b)
{
u16 tmp;
tmp = cpu_to_le16(b);
buf.write((const char *) &tmp, 2);
}
inline void sd_write32(std::ostringstream &buf, u32 b)
{
u32 tmp;
tmp = cpu_to_le32(b);
buf.write((const char *) &tmp, 4);
}
inline void sd_write64(std::ostringstream &buf, u64 b)
{
u64 tmp;
tmp = cpu_to_le64(b);
buf.write((const char *) &tmp, 8);
}
inline void sd_write_uint8(std::ostringstream &buf, u8 b)
{
sd_write8(buf, SD_U8);
sd_write8(buf, b);
}
inline void sd_write_uint16(std::ostringstream &buf, u16 b)
{
sd_write8(buf, SD_U16);
sd_write16(buf, b);
}
inline void sd_write_uint32(std::ostringstream &buf, u32 b)
{
sd_write8(buf, SD_U32);
sd_write32(buf, b);
}
inline void sd_write_uint64(std::ostringstream &buf, u64 b)
{
sd_write8(buf, SD_U64);
sd_write64(buf, b);
}
inline void sd_write_name(std::ostringstream &buf, const char *name)
{
PDEBUG("Writing name '%s'\n", name);
if (name) {
sd_write8(buf, SD_NAME);
sd_write16(buf, strlen(name) + 1);
buf.write(name, strlen(name) + 1);
}
}
inline void sd_write_blob(std::ostringstream &buf, void *b, int buf_size, char *name)
{
sd_write_name(buf, name);
sd_write8(buf, SD_BLOB);
sd_write32(buf, buf_size);
buf.write((const char *) b, buf_size);
}
static char zeros[64];
#define align64(X) (((X) + (typeof(X)) 7) & ~((typeof(X)) 7))
inline void sd_write_aligned_blob(std::ostringstream &buf, void *b, int b_size,
const char *name)
{
sd_write_name(buf, name);
/* pad calculation MUST come after name is written */
size_t pad = align64(buf.tellp() + ((std::streamoff) 5l)) - (buf.tellp() + ((std::streamoff) 5l));
sd_write8(buf, SD_BLOB);
sd_write32(buf, b_size + pad);
buf.write(zeros, pad);
buf.write((const char *) b, b_size);
}
static void sd_write_strn(std::ostringstream &buf, char *b, int size, const char *name)
{
sd_write_name(buf, name);
sd_write8(buf, SD_STRING);
sd_write16(buf, size);
buf.write(b, size);
}
inline void sd_write_string(std::ostringstream &buf, char *b, const char *name)
{
sd_write_strn(buf, b, strlen(b) + 1, name);
}
inline void sd_write_struct(std::ostringstream &buf, const char *name)
{
sd_write_name(buf, name);
sd_write8(buf, SD_STRUCT);
}
inline void sd_write_structend(std::ostringstream &buf)
{
sd_write8(buf, SD_STRUCTEND);
}
inline void sd_write_array(std::ostringstream &buf, const char *name, int size)
{
sd_write_name(buf, name);
sd_write8(buf, SD_ARRAY);
sd_write16(buf, size);
}
inline void sd_write_arrayend(std::ostringstream &buf)
{
sd_write8(buf, SD_ARRAYEND);
}
inline void sd_write_list(std::ostringstream &buf, const char *name)
{
sd_write_name(buf, name);
sd_write8(buf, SD_LIST);
}
inline void sd_write_listend(std::ostringstream &buf)
{
sd_write8(buf, SD_LISTEND);
}
void sd_serialize_dfa(std::ostringstream &buf, void *dfa, size_t size)
{
if (dfa)
sd_write_aligned_blob(buf, dfa, size, "aadfa");
}
void sd_serialize_rlimits(std::ostringstream &buf, struct aa_rlimits *limits)
{
if (!limits->specified)
return;
sd_write_struct(buf, "rlimits");
sd_write_uint32(buf, limits->specified);
sd_write_array(buf, NULL, RLIM_NLIMITS);
for (int i = 0; i < RLIM_NLIMITS; i++) {
sd_write_uint64(buf, limits->limits[i]);
}
sd_write_arrayend(buf);
sd_write_structend(buf);
}
void sd_serialize_xtable(std::ostringstream &buf, char **table)
{
int count;
if (!table[4])
return;
sd_write_struct(buf, "xtable");
count = 0;
for (int i = 4; i < AA_EXEC_COUNT; i++) {
if (table[i])
count++;
}
sd_write_array(buf, NULL, count);
for (int i = 4; i < count + 4; i++) {
int len = strlen(table[i]) + 1;
/* if its a namespace make sure the second : is overwritten
* with 0, so that the namespace and name are \0 seperated
*/
if (*table[i] == ':') {
char *tmp = table[i] + 1;
strsep(&tmp, ":");
}
sd_write_strn(buf, table[i], len, NULL);
}
sd_write_arrayend(buf);
sd_write_structend(buf);
}
void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
int flattened)
{
uint64_t allowed_caps;
sd_write_struct(buf, "profile");
if (flattened) {
assert(profile->parent);
char *name = (char *) malloc(3 + strlen(profile->name) +
strlen(profile->parent->name));
if (!name)
return;
sprintf(name, "%s//%s", profile->parent->name, profile->name);
sd_write_string(buf, name, NULL);
free(name);
} else {
sd_write_string(buf, profile->name, NULL);
}
/* only emit this if current kernel at least supports "create" */
if (perms_create) {
if (profile->xmatch) {
sd_serialize_dfa(buf, profile->xmatch, profile->xmatch_size);
sd_write_uint32(buf, profile->xmatch_len);
}
}
sd_write_struct(buf, "flags");
/* used to be flags.debug, but that's no longer supported */
sd_write_uint32(buf, profile->flags.hat);
sd_write_uint32(buf, profile->flags.complain);
sd_write_uint32(buf, profile->flags.audit);
sd_write_structend(buf);
if (profile->flags.path) {
int flags = 0;
if (profile->flags.path & PATH_CHROOT_REL)
flags |= 0x8;
if (profile->flags.path & PATH_MEDIATE_DELETED)
flags |= 0x10000;
if (profile->flags.path & PATH_ATTACH)
flags |= 0x4;
if (profile->flags.path & PATH_CHROOT_NSATTACH)
flags |= 0x10;
sd_write_name(buf, "path_flags");
sd_write_uint32(buf, flags);
}
#define low_caps(X) ((u32) ((X) & 0xffffffff))
#define high_caps(X) ((u32) (((X) >> 32) & 0xffffffff))
allowed_caps = (profile->caps.allow) & ~profile->caps.deny;
sd_write_uint32(buf, low_caps(allowed_caps));
sd_write_uint32(buf, low_caps(allowed_caps & profile->caps.audit));
sd_write_uint32(buf, low_caps(profile->caps.deny & profile->caps.quiet));
sd_write_uint32(buf, 0);
sd_write_struct(buf, "caps64");
sd_write_uint32(buf, high_caps(allowed_caps));
sd_write_uint32(buf, high_caps(allowed_caps & profile->caps.audit));
sd_write_uint32(buf, high_caps(profile->caps.deny & profile->caps.quiet));
sd_write_uint32(buf, 0);
sd_write_structend(buf);
sd_serialize_rlimits(buf, &profile->rlimits);
if (profile->net.allow && kernel_supports_network) {
size_t i;
sd_write_array(buf, "net_allowed_af", get_af_max());
for (i = 0; i < get_af_max(); i++) {
u16 allowed = profile->net.allow[i] &
~profile->net.deny[i];
sd_write_uint16(buf, allowed);
sd_write_uint16(buf, allowed & profile->net.audit[i]);
sd_write_uint16(buf, profile->net.deny[i] & profile->net.quiet[i]);
}
sd_write_arrayend(buf);
} else if (profile->net.allow)
pwarn(_("profile %s network rules not enforced\n"), profile->name);
if (profile->policy.dfa) {
sd_write_struct(buf, "policydb");
sd_serialize_dfa(buf, profile->policy.dfa, profile->policy.size);
sd_write_structend(buf);
}
/* either have a single dfa or lists of different entry types */
sd_serialize_dfa(buf, profile->dfa.dfa, profile->dfa.size);
sd_serialize_xtable(buf, profile->exec_table);
sd_write_structend(buf);
}
void sd_serialize_top_profile(std::ostringstream &buf, Profile *profile)
{
uint32_t version;
version = ENCODE_VERSION(force_complain, policy_version,
parser_abi_version, kernel_abi_version);
sd_write_name(buf, "version");
sd_write_uint32(buf, version);
if (profile_ns) {
sd_write_string(buf, profile_ns, "namespace");
} else if (profile->ns) {
sd_write_string(buf, profile->ns, "namespace");
}
sd_serialize_profile(buf, profile, profile->parent ? 1 : 0);
}
int cache_fd = -1;
int __sd_serialize_profile(int option, Profile *prof)
{
int fd = -1;
int error = -ENOMEM, size, wsize;
std::ostringstream work_area;
char *filename = NULL;
switch (option) {
case OPTION_ADD:
if (asprintf(&filename, "%s/.load", subdomainbase) == -1)
goto exit;
if (kernel_load) fd = open(filename, O_WRONLY);
break;
case OPTION_REPLACE:
if (asprintf(&filename, "%s/.replace", subdomainbase) == -1)
goto exit;
if (kernel_load) fd = open(filename, O_WRONLY);
break;
case OPTION_REMOVE:
if (asprintf(&filename, "%s/.remove", subdomainbase) == -1)
goto exit;
if (kernel_load) fd = open(filename, O_WRONLY);
break;
case OPTION_STDOUT:
filename = strdup("stdout");
fd = dup(1);
break;
case OPTION_OFILE:
fd = dup(fileno(ofile));
break;
default:
error = -EINVAL;
goto exit;
break;
}
if (fd < 0 && (kernel_load || option == OPTION_OFILE || option == OPTION_STDOUT)) {
PERROR(_("Unable to open %s - %s\n"), filename,
strerror(errno));
error = -errno;
goto exit;
}
error = 0;
free(filename);
if (option == OPTION_REMOVE) {
char *name, *ns = NULL;
int len = 0;
if (profile_ns) {
len += strlen(profile_ns) + 2;
ns = profile_ns;
} else if (prof->ns) {
len += strlen(prof->ns) + 2;
ns = prof->ns;
}
if (prof->parent) {
name = (char *) malloc(strlen(prof->name) + 3 +
strlen(prof->parent->name) + len);
if (!name) {
PERROR(_("Memory Allocation Error: Unable to remove ^%s\n"), prof->name);
error = -errno;
goto exit;
}
if (ns)
sprintf(name, ":%s:%s//%s", ns,
prof->parent->name, prof->name);
else
sprintf(name, "%s//%s", prof->parent->name,
prof->name);
} else if (ns) {
name = (char *) malloc(len + strlen(prof->name) + 1);
if (!name) {
PERROR(_("Memory Allocation Error: Unable to remove %s:%s."), ns, prof->name);
error = -errno;
goto exit;
}
sprintf(name, ":%s:%s", ns, prof->name);
} else {
name = prof->name;
}
size = strlen(name) + 1;
if (kernel_load) {
wsize = write(fd, name, size);
if (wsize < 0)
error = -errno;
}
if (prof->parent || ns)
free(name);
} else {
sd_serialize_top_profile(work_area, prof);
size = (long) work_area.tellp();
if (kernel_load || option == OPTION_STDOUT || option == OPTION_OFILE) {
std::string tmp = work_area.str();
wsize = write(fd, tmp.c_str(), size);
if (wsize < 0) {
error = -errno;
} else if (wsize < size) {
PERROR(_("%s: Unable to write entire profile entry\n"),
progname);
error = -EIO;
}
}
if (cache_fd != -1) {
std::string tmp = work_area.str();
wsize = write(cache_fd, tmp.c_str(), size);
if (wsize < 0) {
error = -errno;
} else if (wsize < size) {
PERROR(_("%s: Unable to write entire profile entry to cache\n"),
progname);
error = -EIO;
}
}
}
close(fd);
if (!prof->hat_table.empty() && option != OPTION_REMOVE) {
if (load_flattened_hats(prof, option) == 0)
return 0;
}
exit:
return error;
}
/* bleah the kernel should just loop and do multiple load, but to support
* older systems we need to do this
*/
#define PROFILE_HEADER_SIZE
static char header_version[] = "\x04\x08\x00version";
static char *next_profile_buffer(char *buffer, int size)
{
char *b = buffer;
for (; size - sizeof(header_version); b++, size--) {
if (memcmp(b, header_version, sizeof(header_version)) == 0) {
return b;
}
}
return NULL;
}
static int write_buffer(int fd, char *buffer, int size, bool set)
{
const char *err_str = set ? "profile set" : "profile";
int wsize = write(fd, buffer, size);
if (wsize < 0) {
PERROR(_("%s: Unable to write %s\n"), progname, err_str);
return -errno;
} else if (wsize < size) {
PERROR(_("%s: Unable to write %s\n"), progname, err_str);
return -EPROTO;
}
return 0;
}
int sd_load_buffer(int option, char *buffer, int size)
{
int fd = -1;
int error, bsize;
char *filename = NULL;
/* TODO: push backup into caller */
if (!kernel_load)
return 0;
switch (option) {
case OPTION_ADD:
if (asprintf(&filename, "%s/.load", subdomainbase) == -1)
return -ENOMEM;
break;
case OPTION_REPLACE:
if (asprintf(&filename, "%s/.replace", subdomainbase) == -1)
return -ENOMEM;
break;
default:
return -EINVAL;
}
fd = open(filename, O_WRONLY);
if (fd < 0) {
PERROR(_("Unable to open %s - %s\n"), filename,
strerror(errno));
error = -errno;
goto out;
}
if (kernel_supports_setload) {
error = write_buffer(fd, buffer, size, true);
} else {
char *b, *next;
error = 0; /* in case there are no profiles */
for (b = buffer; b; b = next, size -= bsize) {
next = next_profile_buffer(b + sizeof(header_version),
size);
if (next)
bsize = next - b;
else
bsize = size;
error = write_buffer(fd, b, bsize, false);
if (error)
break;
}
}
close(fd);
out:
free(filename);
return error;
}