apparmor/kernel-patches/2.6.26/apparmor-rlimits.diff

462 lines
13 KiB
Diff
Raw Normal View History

From: John Johansen <jjohansen@suse.de>
Subject: AppArmor: per profile controls for system rlimits
Provide contol of rlimits on a per profile basis. Each profile provides
a per limit contol and corresponding hard limit value, such that when a
profile becomes attached to a task it sets the tasks limits to be <= to
the profiles specified limits. Note: the profile limit value will not
raise a tasks limit if it is already less than the profile mandates.
In addition to setting a tasks limits, the ability to set limits on
a confined task are controlled. AppArmor only controls the raising
of a tasks limits Tasks with CAP_SYS_RESOURCE can have their hard limits
raised up to the value specified by the profile. AppArmor does not
prevent a task for lowering its hard limits, nor does it provide
additional control on soft limits.
AppArmor only controls the limits specified in a profile so that
any limit not specified is free to be modified subject to standard
linux limitations.
---
security/apparmor/apparmor.h | 23 ++++++
security/apparmor/apparmorfs.c | 2
security/apparmor/lsm.c | 16 ++++
security/apparmor/main.c | 132 +++++++++++++++++++++++++++++++----
security/apparmor/module_interface.c | 56 ++++++++++++++
5 files changed, 215 insertions(+), 14 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>
@@ -139,6 +140,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
@@ -173,6 +186,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
@@ -210,6 +225,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;
@@ -261,6 +279,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;
@@ -328,6 +347,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/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -105,7 +105,7 @@ static ssize_t aa_features_read(struct f
{
const char *features = "file=3.0 capability=2.0 network=1.0 "
"change_hat=1.5 change_profile=1.0 "
- "aanamespaces=1.0";
+ "aanamespaces=1.0 rlimit=1.0";
return simple_read_from_buffer(buf, size, ppos, features,
strlen(features));
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -866,6 +866,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,
@@ -907,6 +922,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
@@ -177,6 +177,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;
@@ -872,6 +875,79 @@ 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 && (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
@@ -885,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;
@@ -894,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) {
@@ -910,18 +992,22 @@ repeat:
goto repeat;
}
+ if (aa_rlimit_nproc(profile)) {
+ sa.info = "rlimit nproc limit exceeded";
+ unlock_profile(profile);
+ aa_audit_reject(profile, &sa);
+ aa_put_profile(profile);
+ return -EAGAIN;
+ }
+
/* 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);
@@ -1156,6 +1242,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;
}
@@ -1303,6 +1393,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);
@@ -1481,17 +1577,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)
@@ -1499,8 +1596,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;
}
/**
@@ -1565,6 +1669,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) {
@@ -1576,6 +1681,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;
@@ -312,6 +328,39 @@ fail:
return 0;
}
+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 = 0;
+ 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 = 0;
+ 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
@@ -355,6 +404,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)
@@ -614,6 +666,8 @@ ssize_t aa_replace_profile(void *udata,
sa.operation = "profile_load";
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
@@ -634,6 +688,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);
@@ -656,6 +711,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)
{