mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 08:24:42 +01:00
461 lines
13 KiB
Diff
461 lines
13 KiB
Diff
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)
|
|
{
|