From: John Johansen 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 #include #include +#include #include #include @@ -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) {