[PATCH 06/12] AppArmor: policy routines for loading and unpacking policy

John Johansen
2009-11-10T16:13:36Z

AppArmor policy is loaded in a platform independent flattened binary
stream. Verify and unpack the data converting it to the internal
format needed for enforcement.

The current routines are compatible with older AppArmor policy. There
are several planned improvements, to reduce the amount of unpacking
and unaligned accesses being done by moving to more aligned tables
that can be shared similar to what is done currently with dfa tables.

Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/include/policy_unpack.h | 30 ++
security/apparmor/policy_unpack.c | 602 +++++++++++++++++++++++++++++
2 files changed, 632 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/policy_unpack.h
create mode 100644 security/apparmor/policy_unpack.c

diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
new file mode 100644
index 0000000..1c02ce6
--- /dev/null
+++ b/security/apparmor/include/policy_unpack.h
@@ -0,0 +1,30 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy loading interface function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __POLICY_INTERFACE_H
+#define __POLICY_INTERFACE_H
+
+struct aa_audit_iface {
+ struct aa_audit base;
+
+ const char *name;
+ const char *name2;
+ long pos;
+};
+
+int aa_audit_iface(struct aa_audit_iface *sa);
+struct aa_profile *aa_unpack(void *udata, size_t size,
+ struct aa_audit_iface *sa);
+
+#endif /* __POLICY_INTERFACE_H */
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
new file mode 100644
index 0000000..9b64f21
--- /dev/null
+++ b/security/apparmor/policy_unpack.c
@@ -0,0 +1,602 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions for unpacking policy loaded from
+ * userspace.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor uses a serialized binary format for loading policy.
+ * The policy format is documented in Documentation/???
+ * All policy is validated all before it is used.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/errno.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/match.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+#include "include/sid.h"
+
+/*
+ * The AppArmor interface treats data as a type byte followed by the
+ * actual data. The interface has the notion of a a named entry
+ * which has a name (AA_NAME typecode followed by name string) followed by
+ * the entries typecode and data. Named types allow for optional
+ * elements and extensions to be added and tested for without breaking
+ * backwards compatability.
+ */
+
+enum aa_code {
+ AA_U8,
+ AA_U16,
+ AA_U32,
+ AA_U64,
+ AA_NAME, /* same as string except it is items name */
+ AA_STRING,
+ AA_BLOB,
+ AA_STRUCT,
+ AA_STRUCTEND,
+ AA_LIST,
+ AA_LISTEND,
+ AA_ARRAY,
+ AA_ARRAYEND,
+};
+
+/*
+ * aa_ext is the read of the buffer containing the serialized profile. The
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
+ * the unpack routines.
+ */
+struct aa_ext {
+ void *start;
+ void *end;
+ void *pos; /* pointer to current position in the buffer */
+ u32 version;
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_iface *sa = va;
+
+ if (sa->name)
+ audit_log_format(ab, " name=%s", sa->name);
+ if (sa->name2)
+ audit_log_format(ab, " namespace=%s", sa->name2);
+ if (sa->base.error && sa->pos)
+ audit_log_format(ab, " offset=%ld", sa->pos);
+}
+
+int aa_audit_iface(struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile;
+ struct cred *cred = aa_get_task_policy(current, &profile);
+ int error = aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa->base,
+ audit_cb);
+ put_cred(cred);
+ return error;
+}
+
+static int aa_inbounds(struct aa_ext *e, size_t size)
+{
+ return (size <= e->end - e->pos);
+}
+
+/**
+ * aa_u16_chunck - test and do bounds checking for a u16 size based chunk
+ * @e: serialized data read head
+ * @chunk: start address for chunk of data
+ *
+ * return the size of chunk found with the read head at the end of
+ * the chunk.
+ */
+static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
+{
+ size_t size = 0;
+
+ if (!aa_inbounds(e, sizeof(u16)))
+ return 0;
+ size = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ if (!aa_inbounds(e, size))
+ return 0;
+ *chunk = e->pos;
+ e->pos += size;
+ return size;
+}
+
+static int unpack_X(struct aa_ext *e, enum aa_code code)
+{
+ if (!aa_inbounds(e, 1))
+ return 0;
+ if (*(u8 *) e->pos != code)
+ return 0;
+ e->pos++;
+ return 1;
+}
+
+/**
+ * unpack_nameX - check is the next element is of type X with a name of @name
+ * @e: serialized data extent information
+ * @code: type code
+ * @name: name to match to the serialized element.
+ *
+ * check that the next serialized data element is of type X and has a tag
+ * name @name. If @name is specified then there must be a matching
+ * name element in the stream. If @name is NULL any name element will be
+ * skipped and only the typecode will be tested.
+ * returns 1 on success (both type code and name tests match) and the read
+ * head is advanced past the headers
+ * returns %0 if either match failes, the read head does not move
+ */
+static int unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
+{
+ /*
+ * May need to rest pos if name or type doesn't match, could be
+ * an optional component.
+ */
+ void *pos = e->pos;
+ /*
+ * Check for presence of a tagname, and if present name size
+ * AA_NAME tag value is a u16.
+ */
+ if (unpack_X(e, AA_NAME)) {
+ char *tag = NULL;
+ size_t size = unpack_u16_chunk(e, &tag);
+ /* if a name is specified it must match. otherwise skip tag */
+ if (name && (!size || strcmp(name, tag)))
+ goto fail;
+ } else if (name) {
+ /* if a name is specified and there is no name tag fail */
+ goto fail;
+ }
+
+ /* now check if type code matches */
+ if (unpack_X(e, code))
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U16, name)) {
+ if (!aa_inbounds(e, sizeof(u16)))
+ return 0;
+ if (data)
+ *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return 1;
+ }
+ return 0;
+}
+
+static int unpack_u32(struct aa_ext *e, u32 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U32, name)) {
+ if (!aa_inbounds(e, sizeof(u32)))
+ return 0;
+ if (data)
+ *data = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ return 1;
+ }
+ return 0;
+}
+
+static int unpack_u64(struct aa_ext *e, u64 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U64, name)) {
+ if (!aa_inbounds(e, sizeof(u64)))
+ return 0;
+ if (data)
+ *data = le64_to_cpu(get_unaligned((u64 *) e->pos));
+ e->pos += sizeof(u64);
+ return 1;
+ }
+ return 0;
+}
+
+static size_t unpack_array(struct aa_ext *e, const char *name)
+{
+ if (unpack_nameX(e, AA_ARRAY, name)) {
+ int size;
+ if (!aa_inbounds(e, sizeof(u16)))
+ return 0;
+ size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return size;
+ }
+ return 0;
+}
+
+static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
+{
+ if (unpack_nameX(e, AA_BLOB, name)) {
+ u32 size;
+ if (!aa_inbounds(e, sizeof(u32)))
+ return 0;
+ size = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ if (aa_inbounds(e, (size_t) size)) {
+ *blob = e->pos;
+ e->pos += size;
+ return size;
+ }
+ }
+ return 0;
+}
+
+static int unpack_string(struct aa_ext *e, char **string, const char *name)
+{
+ char *src_str;
+ size_t size = 0;
+ void *pos = e->pos;
+ *string = NULL;
+ if (unpack_nameX(e, AA_STRING, name)) {
+ size = unpack_u16_chunk(e, &src_str);
+ if (size) {
+ /* strings are null terminated, length is size - 1 */
+ if (src_str[size - 1] != 0)
+ goto fail;
+ *string = src_str;
+ }
+ }
+ return size;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_dynstring(struct aa_ext *e, char **string, const char *name)
+{
+ char *tmp;
+ void *pos = e->pos;
+ int res = unpack_string(e, &tmp, name);
+ *string = NULL;
+
+ if (!res)
+ return 0;
+
+ *string = kstrdup(tmp, GFP_KERNEL);
+ if (!*string) {
+ e->pos = pos;
+ return 0;
+ }
+
+ return res;
+}
+
+/**
+ * aa_unpack_dfa - unpack a file rule dfa
+ * @e: serialized data extent information
+ *
+ * returns dfa or ERR_PTR
+ */
+static struct aa_dfa *aa_unpack_dfa(struct aa_ext *e)
+{
+ char *blob = NULL;
+ size_t size, error = 0;
+ struct aa_dfa *dfa = NULL;
+
+ size = unpack_blob(e, &blob, "aadfa");
+ if (size) {
+ dfa = aa_match_alloc();
+ if (dfa) {
+ /*
+ * The dfa is aligned with in the blob to 8 bytes
+ * from the beginning of the stream.
+ */
+ size_t sz = blob - (char *)e->start;
+ size_t pad = ALIGN(sz, 8) - sz;
+ error = unpack_dfa(dfa, blob + pad, size - pad);
+ if (!error)
+ error = verify_dfa(dfa);
+ } else {
+ error = -ENOMEM;
+ }
+
+ if (error) {
+ aa_match_free(dfa);
+ dfa = ERR_PTR(error);
+ }
+ }
+
+ return dfa;
+}
+
+static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* exec table is optional */
+ if (unpack_nameX(e, AA_STRUCT, "xtable")) {
+ int i, size;
+
+ size = unpack_array(e, NULL);
+ /* currently 4 exec bits and entries 0-3 are reserved iupcx */
+ if (size > 16 - 4)
+ goto fail;
+ profile->file.trans.table = kzalloc(sizeof(char *) * size,
+ GFP_KERNEL);
+ if (!profile->file.trans.table)
+ goto fail;
+
+ profile->file.trans.size = size;
+ for (i = 0; i < size; i++) {
+ char *tmp;
+ if (!unpack_dynstring(e, &tmp, NULL))
+ goto fail;
+ /*
+ * note: strings beginning with a : have an embedded
+ * \0 seperating the profile ns name from the profile
+ * name
+ */
+ profile->file.trans.table[i] = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ if (profile->file.trans.table) {
+ int i;
+ for (i = 0; i < profile->file.trans.size; i++)
+ kfree(profile->file.trans.table[i]);
+ }
+ e->pos = pos;
+ return 0;
+}
+
+int aa_unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* rlimits are optional */
+ if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
+ int i, size;
+ u32 tmp = 0;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.mask = tmp;
+
+ size = unpack_array(e, NULL);
+ if (size > RLIM_NLIMITS)
+ goto fail;
+ for (i = 0; i < size; i++) {
+ u64 tmp = 0;
+ if (!unpack_u64(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.limits[i].rlim_max = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/**
+ * aa_unpack_profile - unpack a serialized profile
+ * @e: serialized data extent information
+ * @sa: audit struct for the operation
+ */
+static struct aa_profile *aa_unpack_profile(struct aa_ext *e,
+ struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile = NULL;
+ char *name;
+ size_t size = 0;
+ int i, error = -EPROTO;
+ u32 tmp;
+ u64 tmp64;
+
+ /* check that we have the right struct being passed */
+ if (!unpack_nameX(e, AA_STRUCT, "profile"))
+ goto fail;
+ if (!unpack_string(e, &name, NULL))
+ goto fail;
+
+ profile = alloc_aa_profile(name);
+ if (!profile)
+ return ERR_PTR(-ENOMEM);
+
+ /* xmatch is optional and may be NULL */
+ profile->xmatch = aa_unpack_dfa(e);
+ if (IS_ERR(profile->xmatch)) {
+ error = PTR_ERR(profile->xmatch);
+ profile->xmatch = NULL;
+ goto fail;
+ }
+ /* xmatch_len is not optional is xmatch is set */
+ if (profile->xmatch && !unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->xmatch_len = tmp;
+
+ /* per profile debug flags (complain, audit) */
+ if (!unpack_nameX(e, AA_STRUCT, "flags"))
+ goto fail;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->flags |= PFLAG_HAT;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->mode = APPARMOR_COMPLAIN;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->audit = AUDIT_ALL;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ /* mmap_min_addr is optional */
+ if (unpack_u64(e, &tmp64, "mmap_min_addr")) {
+ profile->mmap_min_addr = (unsigned long)tmp64;
+ if (((u64) profile->mmap_min_addr) == tmp64) {
+ profile->flags |= PFLAG_MMAP_MIN_ADDR;
+ } else {
+ sa->base.info = "invalid set mmap_min_addr";
+ goto fail;
+ }
+ }
+
+ if (!unpack_u32(e, &(profile->caps.allowed.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.set.cap[0]), NULL))
+ goto fail;
+
+ if (unpack_nameX(e, AA_STRUCT, "caps64")) {
+ /* optional upper half of 64 bit caps */
+ if (!unpack_u32(e, &(profile->caps.allowed.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.set.cap[1]), NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+
+ if (!aa_unpack_rlimits(e, profile))
+ goto fail;
+
+ size = unpack_array(e, "net_allowed_af");
+ if (size) {
+ if (size > AF_MAX)
+ goto fail;
+
+ for (i = 0; i < size; i++) {
+ if (!unpack_u16(e, &profile->net.allowed[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.audit[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ /*
+ * allow unix domain and netlink sockets they are handled
+ * by IPC
+ */
+ }
+ profile->net.allowed[AF_UNIX] = 0xffff;
+ profile->net.allowed[AF_NETLINK] = 0xffff;
+
+ /* get file rules */
+ profile->file.dfa = aa_unpack_dfa(e);
+ if (IS_ERR(profile->file.dfa)) {
+ error = PTR_ERR(profile->file.dfa);
+ profile->file.dfa = NULL;
+ goto fail;
+ }
+
+ if (!aa_unpack_trans_table(e, profile))
+ goto fail;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ return profile;
+
+fail:
+ sa->name = profile && profile->base.name ? profile->base.name :
+ "unknown";
+ if (!sa->base.info)
+ sa->base.info = "failed to unpack profile";
+ aa_audit_iface(sa);
+
+ free_aa_profile(profile);
+
+ return ERR_PTR(error);
+}
+
+/**
+ * aa_verify_head - unpack serialized stream header
+ * @e: serialized data read head
+ * @operation: operation header is being verified for
+ *
+ * returns error or 0 if header is good
+ */
+static int aa_verify_header(struct aa_ext *e, struct aa_audit_iface *sa)
+{
+ /* get the interface version */
+ if (!unpack_u32(e, &e->version, "version")) {
+ sa->base.info = "invalid profile format";
+ aa_audit_iface(sa);
+ return -EPROTONOSUPPORT;
+ }
+
+ /* check that the interface version is currently supported */
+ if (e->version != 5) {
+ sa->base.info = "unsupported interface version";
+ aa_audit_iface(sa);
+ return -EPROTONOSUPPORT;
+ }
+
+ /* read the namespace if present */
+ if (!unpack_string(e, &sa->name2, "namespace"))
+ sa->name2 = NULL;
+
+ return 0;
+}
+
+/**
+ * aa_unpack - unpack packed binary profile data loaded from user space
+ * @udata: user data copied to kmem
+ * @size: the size of the user data
+ * @sa: audit struct for unpacking
+ *
+ * Unpack user data and return refcounted allocated profile or ERR_PTR
+ */
+struct aa_profile *aa_unpack(void *udata, size_t size,
+ struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile;
+ int error;
+ struct aa_ext e = {
+ .start = udata,
+ .end = udata + size,
+ .pos = udata,
+ };
+
+ error = aa_verify_header(&e, sa);
+ if (error)
+ return ERR_PTR(error);
+
+ profile = aa_unpack_profile(&e, sa);
+ if (IS_ERR(profile))
+ sa->pos = e.pos - e.start;
+ /* return refcount */
+ return profile;
+}
--
1.6.3.3