A fix-dfa.diff

- rework how null transitions are done.

M    fix-profile-namespaces.diff
- fix namespaces to use the :namespace: syntax

A    cap-set.diff
- allow a profile to set a tasks capabilities similar to fscap

A    rlimits.diff
- allow control of a tasks rlimits
This commit is contained in:
John Johansen 2008-04-06 18:50:37 +00:00
parent 5f5aeee472
commit 6f65e6e8f5
5 changed files with 663 additions and 10 deletions

View file

@ -0,0 +1,87 @@
---
security/apparmor/apparmor.h | 10 ++++++++++
security/apparmor/apparmorfs.c | 4 ++--
security/apparmor/lsm.c | 15 ++++++---------
security/apparmor/module_interface.c | 2 ++
4 files changed, 20 insertions(+), 11 deletions(-)
--- a/security/apparmor/apparmor.h
+++ b/security/apparmor/apparmor.h
@@ -158,8 +158,17 @@ struct aa_namespace {
* @file_rules: dfa containing the profiles file rules
* @flags: flags controlling profile behavior
* @isstale: flag indicating if profile is stale
+ * @set_caps: capabilities that are being set
+ * @capabilities: capabilities mask
+ * @audit_caps: caps that are to be audited
+ * @quiet_caps: caps that should not be audited
* @capabilities: capabilities granted by the process
* @count: reference count of the profile
+ * @task_contexts: list of tasks confined by profile
+ * @lock: lock for the task_contexts list
+ * @network_families: basic network permissions
+ * @audit_network: which network permissions to force audit
+ * @quiet_network: which network permissions to quiet rejects
*
* The AppArmor profile contains the basic confinement data. Each profile
* has a name, and all nonstale profile are in a profile namespace.
@@ -183,6 +192,7 @@ struct aa_profile {
} flags;
int isstale;
+ kernel_cap_t set_caps;
kernel_cap_t capabilities;
kernel_cap_t audit_caps;
kernel_cap_t quiet_caps;
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -103,8 +103,8 @@ static struct file_operations apparmorfs
static ssize_t aa_features_read(struct file *file, char __user *buf,
size_t size, loff_t *ppos)
{
- const char *features = "file=3.0 capability=1.0 network=1.0 "
- "change_hat=1.3 change_profile=1.0 "
+ const char *features = "file=3.0 capability=2.0 network=1.0 "
+ "change_hat=1.4 change_profile=1.0 "
"aanamespaces=1.0";
return simple_read_from_buffer(buf, size, ppos, features,
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -210,19 +210,16 @@ static int apparmor_ptrace(struct task_s
static int apparmor_capable(struct task_struct *task, int cap)
{
int error;
+ struct aa_task_context *cxt;
/* cap_capable returns 0 on success, else -EPERM */
error = cap_capable(task, cap);
- if (!error) {
- struct aa_task_context *cxt;
-
- rcu_read_lock();
- cxt = aa_task_context(task);
- if (cxt)
- error = aa_capability(cxt, cap);
- rcu_read_unlock();
- }
+ rcu_read_lock();
+ cxt = aa_task_context(task);
+ if (cxt && (!error || cap_raised(cxt->profile->set_caps, cap)))
+ error = aa_capability(cxt, cap);
+ rcu_read_unlock();
return error;
}
--- a/security/apparmor/module_interface.c
+++ b/security/apparmor/module_interface.c
@@ -316,6 +316,8 @@ static struct aa_profile *aa_unpack_prof
goto fail;
if (!aa_is_u32(e, &(profile->quiet_caps), NULL))
goto fail;
+ if (!aa_is_u32(e, &(profile->set_caps), NULL))
+ goto fail;
size = aa_is_array(e, "net_allowed_af");
if (size) {

View file

@ -0,0 +1,134 @@
---
security/apparmor/match.c | 74 +++++++++++++++++++++++++++++++---------------
1 file changed, 51 insertions(+), 23 deletions(-)
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -213,17 +213,23 @@ void aa_match_free(struct aa_dfa *dfa)
}
/**
- * aa_dfa_next_state - traverse @dfa to find state @str stops at
+ * aa_dfa_next_state_len - traverse @dfa to find state @str stops at
* @dfa: the dfa to match @str against
* @start: the state of the dfa to start matching in
- * @str: the string to match against the dfa
+ * @str: the string of bytes to match against the dfa
+ * @len: length of the string of bytes to match
*
* aa_dfa_next_state will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
+ *
+ * aa_dfa_next_state could be implement using this function by doing
+ * return aa_dfa_next_state_len(dfa, start, str, strlen(str));
+ * but that would require traversing the string twice and be slightly
+ * slower.
*/
-unsigned int aa_dfa_next_state(struct aa_dfa *dfa, unsigned int start,
- const char *str)
+unsigned int aa_dfa_next_state_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -237,7 +243,7 @@ unsigned int aa_dfa_next_state(struct aa
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC - 1]) {
u8 *equiv = EQUIV_TABLE(dfa);
- while (*str) {
+ for (; len; len--) {
pos = base[state] + equiv[(u8)*str++];
if (check[pos] == state)
state = next[pos];
@@ -245,7 +251,7 @@ unsigned int aa_dfa_next_state(struct aa
state = def[state];
}
} else {
- while (*str) {
+ for (; len; len--) {
pos = base[state] + (u8)*str++;
if (check[pos] == state)
state = next[pos];
@@ -257,15 +263,17 @@ unsigned int aa_dfa_next_state(struct aa
}
/**
- * aa_dfa_null_transition - step to next state after null character
- * @dfa: the dfa to match against
+ * aa_dfa_next_state - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
* @start: the state of the dfa to start matching in
+ * @str: the null terminated string of bytes to match against the dfa
*
- * aa_dfa_null_transition transitions to the next state after a null
- * character which is not used in standard matching and is only
- * used to seperate pairs.
+ * aa_dfa_next_state will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
*/
-unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start)
+unsigned int aa_dfa_next_state(struct aa_dfa *dfa, unsigned int start,
+ const char *str)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -273,26 +281,46 @@ unsigned int aa_dfa_null_transition(stru
u16 *check = CHECK_TABLE(dfa);
unsigned int state = start, pos;
+ if (state == 0)
+ return 0;
+
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC - 1]) {
u8 *equiv = EQUIV_TABLE(dfa);
- pos = base[state] + equiv[0];
- if (check[pos] == state)
- state = next[pos];
- else
- state = def[state];
+ while (*str) {
+ pos = base[state] + equiv[(u8)*str++];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
} else {
- pos = base[state] + 0;
- if (check[pos] == state)
- state = next[pos];
- else
- state = def[state];
+ while (*str) {
+ pos = base[state] + (u8)*str++;
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
}
-
return state;
}
/**
+ * aa_dfa_null_transition - step to next state after null character
+ * @dfa: the dfa to match against
+ * @start: the state of the dfa to start matching in
+ *
+ * aa_dfa_null_transition transitions to the next state after a null
+ * character which is not used in standard matching and is only
+ * used to seperate pairs.
+ */
+unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start)
+{
+ return aa_dfa_next_state_len(dfa, start, "", 1);
+}
+
+/**
* aa_dfa_match - find accept perm for @str in @dfa
* @dfa: the dfa to match @str against
* @str: the string to match against the dfa

View file

@ -1,8 +1,8 @@
---
security/apparmor/list.c | 2 +-
security/apparmor/main.c | 19 +++++++++++--------
security/apparmor/main.c | 20 +++++++++++---------
security/apparmor/procattr.c | 13 +++++++------
3 files changed, 19 insertions(+), 15 deletions(-)
3 files changed, 19 insertions(+), 16 deletions(-)
--- a/security/apparmor/list.c
+++ b/security/apparmor/list.c
@ -30,7 +30,7 @@
}
audit_log_end(ab);
@@ -1364,15 +1366,16 @@ repeat:
@@ -1364,15 +1366,15 @@ repeat:
if (hat_name) {
char *name, *profile_name;
@ -39,17 +39,17 @@
- become more generic then this will be needed.
- if (!(aa_match(profile->file_rules, hat_name) &
- AA_CHANGE_PROFILE)) {
+/*
+ int foo = aa_match(profile->file_rules, hat_name, NULL);
- error = -EACCES;
+ if (!PROFILE_COMPLAIN(profile) &&
+ !(aa_match(profile->file_rules, hat_name, NULL) &
+ AA_CHANGE_HAT)) {
+printk("failed to change_hat %s 0x%x\n", hat_name, foo);
error = -EACCES;
+ !(aa_match(profile->file_rules, hat_name, NULL)
+ & AA_CHANGE_HAT)) {
+ /* missing permission to change_hat is treated the
+ * same as a failed hat search */
+ error = -ENOENT;
goto out;
}
- */
+*/
+
if (previous_profile)
profile_name = previous_profile->name;
else

View file

@ -0,0 +1,429 @@
---
security/apparmor/apparmor.h | 23 ++++++
security/apparmor/lsm.c | 16 ++++
security/apparmor/main.c | 132 +++++++++++++++++++++++++++++++----
security/apparmor/module_interface.c | 56 ++++++++++++++
4 files changed, 214 insertions(+), 13 deletions(-)
--- a/security/apparmor/apparmor.h
+++ b/security/apparmor/apparmor.h
@@ -16,6 +16,7 @@
#include <linux/fs.h>
#include <linux/binfmts.h>
#include <linux/rcupdate.h>
+#include <linux/resource.h>
#include <linux/socket.h>
#include <net/sock.h>
@@ -129,6 +130,18 @@ extern unsigned int apparmor_path_max;
#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
+/* struct aa_rlimit - rlimits settings for the profile
+ * @mask: which hard limits to set
+ * @limits: rlimit values that override task limits
+ *
+ * AppArmor rlimits are used to set confined task rlimits. Only the
+ * limits specified in @mask will be controlled by apparmor.
+ */
+struct aa_rlimit {
+ unsigned int mask;
+ struct rlimit limits[RLIM_NLIMITS];
+};
+
struct aa_profile;
/* struct aa_namespace - namespace for a set of profiles
@@ -163,6 +176,8 @@ struct aa_namespace {
* @audit_caps: caps that are to be audited
* @quiet_caps: caps that should not be audited
* @capabilities: capabilities granted by the process
+ * @rlimits: rlimits for the profile
+ * @task_count: how many tasks the profile is attached to
* @count: reference count of the profile
* @task_contexts: list of tasks confined by profile
* @lock: lock for the task_contexts list
@@ -197,6 +212,9 @@ struct aa_profile {
kernel_cap_t audit_caps;
kernel_cap_t quiet_caps;
+ struct aa_rlimit rlimits;
+ unsigned int task_count;
+
struct kref count;
struct list_head task_contexts;
spinlock_t lock;
@@ -248,6 +266,7 @@ struct aa_audit {
const char *name2;
const char *name3;
int request_mask, denied_mask, audit_mask;
+ int rlimit;
struct iattr *iattr;
pid_t task, parent;
int family, type, protocol;
@@ -315,6 +334,10 @@ extern int aa_may_ptrace(struct aa_task_
extern int aa_net_perm(struct aa_profile *profile, char *operation,
int family, int type, int protocol);
extern int aa_revalidate_sk(struct sock *sk, char *operation);
+extern int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim);
+extern void aa_set_rlimits(struct task_struct *task, struct aa_profile *profile);
+
/* lsm.c */
extern int apparmor_initialized;
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -856,6 +856,21 @@ static int apparmor_setprocattr(struct t
return error;
}
+static int apparmor_task_setrlimit(unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ profile = aa_get_profile(current);
+ if (profile) {
+ error = aa_task_setrlimit(profile, resource, new_rlim);
+ }
+ aa_put_profile(profile);
+
+ return error;
+}
+
struct security_operations apparmor_ops = {
.ptrace = apparmor_ptrace,
.capget = cap_capget,
@@ -897,6 +912,7 @@ struct security_operations apparmor_ops
.task_free_security = apparmor_task_free_security,
.task_post_setuid = cap_task_post_setuid,
.task_reparent_to_init = cap_task_reparent_to_init,
+ .task_setrlimit = apparmor_task_setrlimit,
.getprocattr = apparmor_getprocattr,
.setprocattr = apparmor_setprocattr,
--- a/security/apparmor/main.c
+++ b/security/apparmor/main.c
@@ -196,6 +196,9 @@ static int aa_audit_base(struct aa_profi
if (sa->request_mask)
audit_log_format(ab, " fsuid=%d", current->fsuid);
+ if (sa->rlimit)
+ audit_log_format(ab, " rlimit=%d", sa->rlimit - 1);
+
if (sa->iattr) {
struct iattr *iattr = sa->iattr;
@@ -873,6 +876,78 @@ int aa_revalidate_sk(struct sock *sk, ch
return error;
}
+/**
+ * aa_task_setrlimit - test permission to set an rlimit
+ * @profile - profile confining the task
+ * @resource - the resource being set
+ * @new_rlim - the new resource limit
+ *
+ * Control raising the processes hard limit.
+ */
+int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ struct aa_audit sa;
+ int error = 0;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.operation = "setrlimit";
+ sa.gfp_mask = GFP_KERNEL;
+ sa.rlimit = resource + 1;
+
+ if (profile->rlimits.mask & (1 << resource) &&
+ new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)
+ sa.error_code = -EACCES;
+
+ error = aa_audit(profile, &sa);
+
+ return error;
+}
+
+static int aa_rlimit_nproc(struct aa_profile *profile) {
+ if ((profile->rlimits.mask & (1 << RLIMIT_NPROC)) &&
+ profile->task_count >= profile->rlimits.limits[RLIMIT_NPROC].rlim_max)
+ return -EAGAIN;
+ return 0;
+}
+
+void aa_set_rlimits(struct task_struct *task, struct aa_profile *profile)
+{
+ int i, mask;
+
+ if (!profile)
+ return;
+
+ if (!profile->rlimits.mask)
+ return;
+
+ task_lock(task->group_leader);
+ mask = 1;
+ for (i = 0; i < RLIM_NLIMITS; i++, mask <<= 1) {
+ struct rlimit new_rlim, *old_rlim;
+
+ /* check to see if NPROC which is per profile and handled
+ * in clone/exec or whether this is a limit to be set
+ * can't set cpu limit either right now
+ */
+ if (i == RLIMIT_NPROC || i == RLIMIT_CPU)
+ continue;
+
+ old_rlim = task->signal->rlim + i;
+ new_rlim = *old_rlim;
+
+ if (mask & profile->rlimits.mask &&
+ profile->rlimits.limits[i].rlim_max < new_rlim.rlim_max) {
+ new_rlim.rlim_max = profile->rlimits.limits[i].rlim_max;
+ /* soft limit should not exceed hard limit */
+ if (new_rlim.rlim_cur > new_rlim.rlim_max)
+ new_rlim.rlim_cur = new_rlim.rlim_max;
+ }
+
+ *old_rlim = new_rlim;
+ }
+ task_unlock(task->group_leader);
+}
/*******************************
* Global task related functions
@@ -886,6 +961,7 @@ int aa_revalidate_sk(struct sock *sk, ch
*/
int aa_clone(struct task_struct *child)
{
+ struct aa_audit sa;
struct aa_task_context *cxt, *child_cxt;
struct aa_profile *profile;
@@ -895,6 +971,11 @@ int aa_clone(struct task_struct *child)
if (!child_cxt)
return -ENOMEM;
+ memset(&sa, 0, sizeof(sa));
+ sa.operation = "clone";
+ sa.task = child->pid;
+ sa.gfp_mask = GFP_KERNEL;
+
repeat:
profile = aa_get_profile(current);
if (profile) {
@@ -911,18 +992,23 @@ repeat:
goto repeat;
}
+ sa.error_code = aa_rlimit_nproc(profile);
+ if (sa.error_code) {
+ sa.info = "rlimit nproc limit exceeded";
+ unlock_profile(profile);
+ aa_audit_reject(profile, &sa);
+ aa_put_profile(profile);
+ return sa.error_code;
+ }
+
/* No need to grab the child's task lock here. */
aa_change_task_context(child, child_cxt, profile,
cxt->cookie, cxt->previous_profile);
+
unlock_profile(profile);
if (APPARMOR_COMPLAIN(child_cxt) &&
profile == profile->ns->null_complain_profile) {
- struct aa_audit sa;
- memset(&sa, 0, sizeof(sa));
- sa.operation = "clone";
- sa.gfp_mask = GFP_KERNEL;
- sa.task = child->pid;
aa_audit_hint(profile, &sa);
}
aa_put_profile(profile);
@@ -1100,6 +1186,10 @@ repeat:
sa.task = current->parent->pid;
aa_audit_reject(profile, &sa);
}
+ if (PTR_ERR(old_profile) == -EAGAIN) {
+ sa.info = "rlimit nproc limit exceeded";
+ aa_audit_reject(profile, &sa);
+ }
new_profile = old_profile;
goto cleanup;
}
@@ -1239,6 +1329,12 @@ static int do_change_profile(struct aa_p
goto out;
}
+ if ((error = aa_rlimit_nproc(new_profile))) {
+ sa->info = "rlimit nproc limit exceeded";
+ aa_audit_reject(cxt->profile, sa);
+ goto out;
+ }
+
if (new_profile == ns->null_complain_profile)
aa_audit_hint(cxt->profile, sa);
@@ -1427,17 +1523,18 @@ struct aa_profile *__aa_replace_profile(
cxt = lock_task_and_profiles(task, profile);
if (unlikely(profile && profile->isstale)) {
- task_unlock(task);
- unlock_both_profiles(profile, cxt ? cxt->profile : NULL);
- aa_free_task_context(new_cxt);
- return ERR_PTR(-ESTALE);
+ old_profile = ERR_PTR(-ESTALE);
+ goto error;
}
if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile)) {
- task_unlock(task);
- unlock_both_profiles(profile, cxt ? cxt->profile : NULL);
- aa_free_task_context(new_cxt);
- return ERR_PTR(-EPERM);
+ old_profile = ERR_PTR(-EPERM);
+ goto error;
+ }
+
+ if (aa_rlimit_nproc(profile)) {
+ old_profile = ERR_PTR(-EAGAIN);
+ goto error;
}
if (cxt)
@@ -1445,8 +1542,15 @@ struct aa_profile *__aa_replace_profile(
aa_change_task_context(task, new_cxt, profile, 0, NULL);
task_unlock(task);
+ aa_set_rlimits(task, profile);
unlock_both_profiles(profile, old_profile);
return old_profile;
+
+error:
+ task_unlock(task);
+ unlock_both_profiles(profile, cxt ? cxt->profile : NULL);
+ aa_free_task_context(new_cxt);
+ return old_profile;
}
/**
@@ -1511,6 +1615,7 @@ void aa_change_task_context(struct task_
if (old_cxt) {
list_del_init(&old_cxt->list);
+ old_cxt->profile->task_count--;
call_rcu(&old_cxt->rcu, free_aa_task_context_rcu_callback);
}
if (new_cxt) {
@@ -1522,6 +1627,7 @@ void aa_change_task_context(struct task_
new_cxt->cookie = cookie;
new_cxt->task = task;
new_cxt->profile = aa_dup_profile(profile);
+ profile->task_count++;
new_cxt->previous_profile = aa_dup_profile(previous_profile);
list_move(&new_cxt->list, &profile->task_contexts);
}
--- a/security/apparmor/module_interface.c
+++ b/security/apparmor/module_interface.c
@@ -177,6 +177,22 @@ fail:
return 0;
}
+static int aa_is_u64(struct aa_ext *e, u64 *data, const char *name)
+{
+ void *pos = e->pos;
+ if (aa_is_nameX(e, AA_U64, name)) {
+ if (!aa_inbounds(e, sizeof(u64)))
+ goto fail;
+ if (data)
+ *data = le64_to_cpu(get_unaligned((u64 *)e->pos));
+ e->pos += sizeof(u64);
+ return 1;
+ }
+fail:
+ e->pos = pos;
+ return 0;
+}
+
static size_t aa_is_array(struct aa_ext *e, const char *name)
{
void *pos = e->pos;
@@ -273,6 +289,39 @@ struct aa_dfa *aa_unpack_dfa(struct aa_e
return dfa;
}
+int aa_unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* rlimits are optional */
+ if (aa_is_nameX(e, AA_STRUCT, "rlimits")) {
+ int i, size;
+ u32 tmp;
+ if (!aa_is_u32(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.mask = tmp;
+
+ size = aa_is_array(e, NULL);
+ if (size != RLIM_NLIMITS)
+ goto fail;
+ for (i = 0; i < size; i++) {
+ u64 tmp;
+ if (!aa_is_u64(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.limits[i].rlim_max = tmp;
+ }
+ if (!aa_is_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!aa_is_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
@@ -319,6 +368,9 @@ static struct aa_profile *aa_unpack_prof
if (!aa_is_u32(e, &(profile->set_caps), NULL))
goto fail;
+ if (!aa_unpack_rlimits(e, profile))
+ goto fail;
+
size = aa_is_array(e, "net_allowed_af");
if (size) {
if (size > AF_MAX)
@@ -565,6 +617,8 @@ ssize_t aa_replace_profile(void *udata,
if (!old_profile)
goto out;
+ /* do not fail replacement based off of profile's NPROC rlimit */
+
/*
* Replacement needs to allocate a new aa_task_context for each
* task confined by old_profile. To do this the profile locks
@@ -585,6 +639,7 @@ ssize_t aa_replace_profile(void *udata,
task_lock(task);
task_replace(task, new_cxt, new_profile);
task_unlock(task);
+ aa_set_rlimits(task, new_profile);
new_cxt = NULL;
}
unlock_both_profiles(old_profile, new_profile);
@@ -604,6 +659,7 @@ out:
*
* remove a profile from the profile list and all aa_task_context references
* to said profile.
+ * NOTE: removing confinement does not restore rlimits to preconfinemnet values
*/
ssize_t aa_remove_profile(char *name, size_t size)
{

View file

@ -107,3 +107,6 @@ audit-log-type-in-syslog.diff
#FS2.1.3_fix-unionfs-with-AppArmor.patch
fix-profile-namespaces.diff
fix-dfa.diff
cap-set.diff
rlimits.diff