mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-05 08:45:22 +00:00
551 lines
16 KiB
Diff
551 lines
16 KiB
Diff
Add change_profile to AppArmor
|
|
|
|
series of multiple patches that are not fully independant
|
|
multiprofile-load - allow interface to load a set of profiles, will be used
|
|
to load hats flattened into a set of profiles
|
|
change_profile - add token_profile to aa_task_context and change_profile call
|
|
flatten_hats - remove hat nesting
|
|
change_hat-to-change_profile - convert change_hat to use change_profile
|
|
ie. needs profile rules? do we want to do
|
|
this?
|
|
|
|
|
|
---
|
|
security/apparmor/apparmor.h | 23 ++-
|
|
security/apparmor/lsm.c | 4
|
|
security/apparmor/main.c | 249 ++++++++++++++++++++++++++++++++++++++-----
|
|
security/apparmor/procattr.c | 49 ++++++--
|
|
4 files changed, 277 insertions(+), 48 deletions(-)
|
|
|
|
--- a/security/apparmor/apparmor.h
|
|
+++ b/security/apparmor/apparmor.h
|
|
@@ -27,6 +27,7 @@
|
|
#define AA_EXEC_PROFILE 0x0080
|
|
#define AA_EXEC_MMAP 0x0100
|
|
#define AA_EXEC_UNSAFE 0x0200
|
|
+#define AA_CHANGE_PROFILE 0x40000000
|
|
|
|
#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \
|
|
AA_EXEC_UNCONFINED | \
|
|
@@ -34,7 +35,8 @@
|
|
|
|
#define AA_VALID_PERM_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | \
|
|
AA_MAY_LINK | AA_EXEC_MODIFIERS | \
|
|
- AA_EXEC_MMAP | AA_EXEC_UNSAFE)
|
|
+ AA_EXEC_MMAP | AA_EXEC_UNSAFE | \
|
|
+ AA_CHANGE_PROFILE)
|
|
|
|
#define AA_SECURE_EXEC_NEEDED 1
|
|
|
|
@@ -124,7 +126,8 @@ extern struct mutex aa_interface_lock;
|
|
/**
|
|
* struct aa_task_context - primary label for confined tasks
|
|
* @profile: the current profile
|
|
- * @hat_magic: the magic token controling the ability to leave a hat
|
|
+ * @token_profile: previous profile that may be returned to by matching @token
|
|
+ * @token: the token controling the ability to return to previous profile
|
|
* @list: list this aa_task_context is on
|
|
* @task: task that the aa_task_context confines
|
|
* @rcu: rcu head used when freeing the aa_task_context
|
|
@@ -135,7 +138,8 @@ extern struct mutex aa_interface_lock;
|
|
*/
|
|
struct aa_task_context {
|
|
struct aa_profile *profile; /* The current profile */
|
|
- u64 hat_magic; /* used with change_hat */
|
|
+ struct aa_profile *token_profile;
|
|
+ u64 token; /* used with change_profile */
|
|
struct list_head list;
|
|
struct task_struct *task;
|
|
struct rcu_head rcu;
|
|
@@ -158,7 +162,7 @@ struct aa_audit {
|
|
int requested_mask, denied_mask;
|
|
struct iattr *iattr;
|
|
pid_t task, parent;
|
|
- u64 magic_token;
|
|
+ u64 token;
|
|
int error_code;
|
|
};
|
|
|
|
@@ -217,16 +221,20 @@ extern int aa_clone(struct task_struct *
|
|
extern int aa_register(struct linux_binprm *bprm);
|
|
extern void aa_release(struct task_struct *task);
|
|
extern int aa_change_hat(const char *id, u64 hat_magic);
|
|
+extern int aa_change_profile(const char *name, u64 token);
|
|
extern struct aa_profile *__aa_find_profile(const char *name,
|
|
struct list_head *list);
|
|
extern struct aa_profile *__aa_replace_profile(struct task_struct *task,
|
|
- struct aa_profile *profile,
|
|
- u32 hat_magic);
|
|
+ struct aa_profile *profile);
|
|
extern struct aa_task_context *lock_task_and_profiles(struct task_struct *task,
|
|
struct aa_profile *profile);
|
|
+extern void unlock_task_and_profiles(struct task_struct *task,
|
|
+ struct aa_task_context *cxt,
|
|
+ struct aa_profile *profile);
|
|
extern void aa_change_task_context(struct task_struct *task,
|
|
struct aa_task_context *new_cxt,
|
|
- struct aa_profile *profile, u64 hat_magic);
|
|
+ struct aa_profile *profile, u64 token,
|
|
+ struct aa_profile *token_profile);
|
|
extern int aa_may_ptrace(struct aa_task_context *cxt,
|
|
struct aa_profile *tracee);
|
|
|
|
@@ -246,6 +254,7 @@ extern void aa_unconfine_tasks(struct aa
|
|
extern int aa_getprocattr(struct aa_profile *profile, char **string,
|
|
unsigned *len);
|
|
extern int aa_setprocattr_changehat(char *args);
|
|
+extern int aa_setprocattr_changeprofile(char *args);
|
|
extern int aa_setprocattr_setprofile(struct task_struct *task, char *args);
|
|
|
|
/* apparmorfs.c */
|
|
--- a/security/apparmor/lsm.c
|
|
+++ b/security/apparmor/lsm.c
|
|
@@ -629,6 +629,10 @@ static int apparmor_setprocattr(struct t
|
|
if (current != task)
|
|
return -EACCES;
|
|
error = aa_setprocattr_changehat(args);
|
|
+ } else if (strcmp(command, "changeprofile") == 0) {
|
|
+ if (current != task)
|
|
+ return -EACCES;
|
|
+ error = aa_setprocattr_changeprofile(args);
|
|
} else if (strcmp(command, "setprofile")) {
|
|
struct aa_profile *profile;
|
|
|
|
--- a/security/apparmor/main.c
|
|
+++ b/security/apparmor/main.c
|
|
@@ -715,7 +715,7 @@ repeat:
|
|
|
|
/* No need to grab the child's task lock here. */
|
|
aa_change_task_context(child, child_cxt, profile,
|
|
- cxt->hat_magic);
|
|
+ cxt->token, cxt->token_profile);
|
|
unlock_profile(profile);
|
|
|
|
if (APPARMOR_COMPLAIN(child_cxt) &&
|
|
@@ -946,12 +946,196 @@ repeat:
|
|
aa_put_profile(profile);
|
|
goto repeat;
|
|
}
|
|
- aa_change_task_context(task, NULL, NULL, 0);
|
|
+ aa_change_task_context(task, NULL, NULL, 0, NULL);
|
|
unlock_profile(profile);
|
|
aa_put_profile(profile);
|
|
}
|
|
}
|
|
|
|
+static int do_change_profile(struct task_struct *task, const char *name,
|
|
+ u64 token, struct aa_audit *sa)
|
|
+{
|
|
+ struct aa_profile *profile = NULL, *token_profile = NULL;
|
|
+ struct aa_task_context *new_cxt, *cxt;
|
|
+ int error = 0;
|
|
+
|
|
+ new_cxt = aa_alloc_task_context(GFP_KERNEL);
|
|
+ if (!new_cxt)
|
|
+ return -ENOMEM;
|
|
+
|
|
+repeat:
|
|
+ profile = aa_find_profile(name);
|
|
+ if (!profile)
|
|
+ /* if we name_profile is set then returning
|
|
+ * and return profile has been removed, so go
|
|
+ * unconfined.
|
|
+ */
|
|
+ profile = aa_dup_profile(null_complain_profile);
|
|
+
|
|
+ cxt = lock_task_and_profiles(task, profile);
|
|
+ if (!cxt) {
|
|
+ error = -EPERM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (unlikely(profile->isstale)) {
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
+ aa_put_profile(profile);
|
|
+ goto repeat;
|
|
+ }
|
|
+
|
|
+ if (cxt->token_profile) {
|
|
+ if (cxt->token != token) {
|
|
+ error = -EACCES;
|
|
+ sa->info = "killing process";
|
|
+ aa_audit_reject(profile, sa);
|
|
+ /* terminate process */
|
|
+ (void)send_sig_info(SIGKILL, NULL, task);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ token_profile = cxt->token_profile;
|
|
+ } else {
|
|
+ token_profile = cxt->profile;
|
|
+ }
|
|
+
|
|
+ if ((task->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile)) {
|
|
+ error = -EACCES;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (profile == null_complain_profile) {
|
|
+ if (APPARMOR_COMPLAIN(cxt)) {
|
|
+ aa_audit_hint(cxt->profile, sa);
|
|
+ } else {
|
|
+ error = -ENOENT;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (APPARMOR_AUDIT(cxt))
|
|
+ aa_audit_message(cxt->profile, sa, AUDIT_APPARMOR_AUDIT);
|
|
+ aa_change_task_context(task, new_cxt, profile, token, token_profile);
|
|
+
|
|
+out:
|
|
+ if (aa_task_context(task) != new_cxt)
|
|
+ aa_free_task_context(new_cxt);
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
+ aa_put_profile(profile);
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int do_restore_profile(struct task_struct *task, u64 token,
|
|
+ struct aa_audit *sa)
|
|
+{
|
|
+ struct aa_profile *profile = NULL;
|
|
+ struct aa_task_context *cxt, *new_cxt, *old_cxt = NULL;
|
|
+ int error = 0;
|
|
+
|
|
+ new_cxt = aa_alloc_task_context(GFP_KERNEL);
|
|
+ if (!new_cxt)
|
|
+ return -ENOMEM;
|
|
+
|
|
+repeat:
|
|
+ cxt = lock_task_and_profiles(task, profile);
|
|
+ if (!cxt) {
|
|
+ error = -EPERM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* ignore returning to stored profile when there isn't one */
|
|
+ if (!cxt->token_profile)
|
|
+ goto out;
|
|
+
|
|
+ if (!profile)
|
|
+ /* setting profile with token_profile is locking safe */
|
|
+ profile = aa_dup_profile(cxt->token_profile);
|
|
+
|
|
+ if (profile->isstale || (old_cxt && old_cxt != cxt)) {
|
|
+ struct aa_profile *token_profile;
|
|
+ token_profile = aa_dup_profile(cxt->token_profile);
|
|
+ old_cxt = cxt;
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
+ aa_put_profile(profile);
|
|
+ profile = aa_find_profile(token_profile->name);
|
|
+ aa_put_profile(token_profile);
|
|
+ goto repeat;
|
|
+ }
|
|
+
|
|
+ if (cxt->token != token) {
|
|
+ error = -EACCES;
|
|
+ sa->info = "killing process";
|
|
+ aa_audit_reject(profile, sa);
|
|
+ /* terminate process */
|
|
+ (void)send_sig_info(SIGKILL, NULL, task);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if ((task->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile))
|
|
+ error = -EACCES;
|
|
+ else {
|
|
+ if (APPARMOR_AUDIT(cxt)) {
|
|
+ sa->name = profile->name;
|
|
+ aa_audit_message(cxt->profile, sa,
|
|
+ AUDIT_APPARMOR_AUDIT);
|
|
+ }
|
|
+ aa_change_task_context(task, new_cxt, profile, 0, NULL);
|
|
+ }
|
|
+out:
|
|
+ if (aa_task_context(task) != new_cxt)
|
|
+ aa_free_task_context(new_cxt);
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
+ aa_put_profile(profile);
|
|
+ return error;
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * aa_change_profile - change profile to/from previous stored profile
|
|
+ * @name: name of profile to change to
|
|
+ * @token: token to validate the profile change
|
|
+ *
|
|
+ * Change to new profile @name, and store the @token in the current task
|
|
+ * context. If the new @name is %NULL and the @token matches that
|
|
+ * stored in the current task context, return to the stored token_profile.
|
|
+ *
|
|
+ * Returns %0 on success, error otherwise.
|
|
+ */
|
|
+int aa_change_profile(const char *name, u64 token)
|
|
+{
|
|
+ struct aa_profile *profile;
|
|
+ struct aa_audit sa;
|
|
+ int error = 0;
|
|
+
|
|
+ memset(&sa, 0, sizeof(sa));
|
|
+ sa.gfp_mask = GFP_ATOMIC;
|
|
+ sa.token = token;
|
|
+ sa.name = name;
|
|
+ sa.operation = "change_profile";
|
|
+
|
|
+ profile = aa_get_profile(current);
|
|
+ if (!profile)
|
|
+ /* an unconfined process can not change_profile */
|
|
+ return -EPERM;
|
|
+
|
|
+ if (name) {
|
|
+ if (profile != null_complain_profile &&
|
|
+ !aa_match(profile->file_rules, name) & AA_CHANGE_PROFILE) {
|
|
+ /* no permission to transition to profile @name */
|
|
+ aa_put_profile(profile);
|
|
+ return -EACCES;
|
|
+ }
|
|
+
|
|
+ error = do_change_profile(current, name, token, &sa);
|
|
+ } else {
|
|
+ error = do_restore_profile(current, token, &sa);
|
|
+ }
|
|
+
|
|
+ aa_put_profile(profile);
|
|
+ return error;
|
|
+}
|
|
+
|
|
+
|
|
/**
|
|
* do_change_hat - actually switch hats
|
|
* @hat_name: name of hat to switch to
|
|
@@ -1091,10 +1275,6 @@ int aa_change_hat(const char *hat_name,
|
|
cxt->hat_magic, &sa);
|
|
}
|
|
} else if (cxt->hat_magic) {
|
|
- sa.info = "killing process";
|
|
- aa_audit_status(profile, &sa);
|
|
- /* terminate current process */
|
|
- (void)send_sig_info(SIGKILL, NULL, current);
|
|
} else { /* cxt->hat_magic == 0 */
|
|
sa.info = "killing process confined to current hat";
|
|
aa_audit_status(profile, &sa);
|
|
@@ -1107,8 +1287,7 @@ int aa_change_hat(const char *hat_name,
|
|
out:
|
|
if (aa_task_context(current) != new_cxt)
|
|
aa_free_task_context(new_cxt);
|
|
- task_unlock(current);
|
|
- unlock_profile(profile);
|
|
+ unlock_task_and_profiles(current, cxt, NULL);
|
|
return error;
|
|
}
|
|
|
|
@@ -1116,14 +1295,12 @@ out:
|
|
* __aa_replace_profile - replace a task's profile
|
|
* @task: task to switch the profile of
|
|
* @profile: profile to switch to
|
|
- * @hat_magic: magic cookie to switch to
|
|
*
|
|
* Returns a handle to the previous profile upon success, or else an
|
|
* error code.
|
|
*/
|
|
struct aa_profile *__aa_replace_profile(struct task_struct *task,
|
|
- struct aa_profile *profile,
|
|
- u32 hat_magic)
|
|
+ struct aa_profile *profile)
|
|
{
|
|
struct aa_task_context *cxt, *new_cxt = NULL;
|
|
struct aa_profile *old_profile = NULL;
|
|
@@ -1136,34 +1313,29 @@ 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);
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
aa_free_task_context(new_cxt);
|
|
return ERR_PTR(-ESTALE);
|
|
}
|
|
|
|
if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile)) {
|
|
- task_unlock(task);
|
|
- unlock_both_profiles(profile, cxt ? cxt->profile : NULL);
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
aa_free_task_context(new_cxt);
|
|
return ERR_PTR(-EPERM);
|
|
}
|
|
|
|
- if (cxt) {
|
|
+ if (cxt)
|
|
old_profile = aa_dup_profile(cxt->profile);
|
|
- aa_change_task_context(task, new_cxt, profile, cxt->hat_magic);
|
|
- } else
|
|
- aa_change_task_context(task, new_cxt, profile, 0);
|
|
+ aa_change_task_context(task, new_cxt, profile, 0, NULL);
|
|
|
|
- task_unlock(task);
|
|
- unlock_both_profiles(profile, old_profile);
|
|
+ unlock_task_and_profiles(task, cxt, profile);
|
|
return old_profile;
|
|
}
|
|
|
|
/**
|
|
- * lock_task_and_profile - lock the task and confining profiles and @profile
|
|
- * @task - task to lock
|
|
- * @profile - extra profile to lock in addition to the current profile
|
|
+ * lock_task_and_profiles - lock the task and confining profiles and @profile
|
|
+ * @task: task to lock
|
|
+ * @profile: extra profile to lock in addition to the current profile
|
|
*
|
|
* Handle the spinning on locking to make sure the task context and
|
|
* profile are consistent once all locks are aquired.
|
|
@@ -1176,12 +1348,16 @@ lock_task_and_profiles(struct task_struc
|
|
{
|
|
struct aa_task_context *cxt;
|
|
struct aa_profile *old_profile = NULL;
|
|
+ int lock_token = !profile;
|
|
|
|
rcu_read_lock();
|
|
repeat:
|
|
cxt = aa_task_context(task);
|
|
- if (cxt)
|
|
+ if (cxt) {
|
|
old_profile = cxt->profile;
|
|
+ if (lock_token)
|
|
+ profile = cxt->token_profile;
|
|
+ }
|
|
lock_both_profiles(profile, old_profile);
|
|
task_lock(task);
|
|
|
|
@@ -1189,12 +1365,26 @@ repeat:
|
|
if (unlikely(cxt != aa_task_context(task))) {
|
|
task_unlock(task);
|
|
unlock_both_profiles(profile, old_profile);
|
|
+ old_profile = NULL;
|
|
+ if (lock_token)
|
|
+ profile = NULL;
|
|
goto repeat;
|
|
}
|
|
rcu_read_unlock();
|
|
return cxt;
|
|
}
|
|
|
|
+void unlock_task_and_profiles(struct task_struct *task,
|
|
+ struct aa_task_context *cxt,
|
|
+ struct aa_profile *profile)
|
|
+{
|
|
+ task_unlock(task);
|
|
+ if (cxt && !profile)
|
|
+ profile = cxt->token_profile;
|
|
+ unlock_both_profiles(profile, cxt ? cxt->profile : NULL);
|
|
+}
|
|
+
|
|
+
|
|
static void free_aa_task_context_rcu_callback(struct rcu_head *head)
|
|
{
|
|
struct aa_task_context *cxt;
|
|
@@ -1208,11 +1398,13 @@ static void free_aa_task_context_rcu_cal
|
|
* @task: task that is having its task context changed
|
|
* @new_cxt: new task context to use after the switch
|
|
* @profile: new profile to use after the switch
|
|
- * @hat_magic: hat value to switch to (0 for no hat)
|
|
+ * @token: token value to switch to
|
|
+ * @token_profile: profile that can be returned to
|
|
*/
|
|
void aa_change_task_context(struct task_struct *task,
|
|
struct aa_task_context *new_cxt,
|
|
- struct aa_profile *profile, u64 hat_magic)
|
|
+ struct aa_profile *profile, u64 token,
|
|
+ struct aa_profile *token_profile)
|
|
{
|
|
struct aa_task_context *old_cxt = aa_task_context(task);
|
|
|
|
@@ -1224,9 +1416,10 @@ void aa_change_task_context(struct task_
|
|
/* clear the caps_logged cache, so that new profile/hat has
|
|
* chance to emit its own set of cap messages */
|
|
new_cxt->caps_logged = CAP_EMPTY_SET;
|
|
- new_cxt->hat_magic = hat_magic;
|
|
+ new_cxt->token = token;
|
|
new_cxt->task = task;
|
|
new_cxt->profile = aa_dup_profile(profile);
|
|
+ new_cxt->token_profile = aa_dup_profile(token_profile);
|
|
list_move(&new_cxt->list, &profile->parent->task_contexts);
|
|
}
|
|
rcu_assign_pointer(task->security, new_cxt);
|
|
--- a/security/apparmor/procattr.c
|
|
+++ b/security/apparmor/procattr.c
|
|
@@ -54,28 +54,51 @@ int aa_getprocattr(struct aa_profile *pr
|
|
return 0;
|
|
}
|
|
|
|
+static char *split_token_from_name(const char *op, char *args, u64 *token)
|
|
+{
|
|
+ char *name;
|
|
+
|
|
+ *token = simple_strtoull(args, &name, 16);
|
|
+ if (name == args || *name != '^') {
|
|
+ AA_ERROR("%s: Invalid input '%s'", op, args);
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+ name++; /* skip ^ */
|
|
+ if (!*name)
|
|
+ name = NULL;
|
|
+ return name;
|
|
+}
|
|
+
|
|
int aa_setprocattr_changehat(char *args)
|
|
{
|
|
char *hat;
|
|
- u64 magic;
|
|
+ u64 token;
|
|
|
|
- magic = simple_strtoull(args, &hat, 16);
|
|
- if (hat == args || *hat != '^') {
|
|
- AA_ERROR("change_hat: Invalid input '%s'", args);
|
|
- return -EINVAL;
|
|
- }
|
|
- hat++; /* skip ^ */
|
|
- if (!*hat)
|
|
- hat = NULL;
|
|
- if (!hat && !magic) {
|
|
+ hat = split_token_from_name("change_hat", args, &token);
|
|
+ if (IS_ERR(hat))
|
|
+ return PTR_ERR(hat);
|
|
+
|
|
+ if (!hat && !token) {
|
|
AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
|
|
return -EINVAL;
|
|
}
|
|
|
|
AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
|
|
- __FUNCTION__, magic, hat ? hat : NULL);
|
|
+ __FUNCTION__, token, hat ? hat : NULL);
|
|
+
|
|
+ return aa_change_hat(hat, token);
|
|
+}
|
|
+
|
|
+int aa_setprocattr_changeprofile(char *args)
|
|
+{
|
|
+ char *name;
|
|
+ u64 token;
|
|
+
|
|
+ name = split_token_from_name("change_profile", args, &token);
|
|
+ if (IS_ERR(name))
|
|
+ return PTR_ERR(name);
|
|
|
|
- return aa_change_hat(hat, magic);
|
|
+ return aa_change_profile(name, token);
|
|
}
|
|
|
|
int aa_setprocattr_setprofile(struct task_struct *task, char *args)
|
|
@@ -104,7 +127,7 @@ repeat:
|
|
}
|
|
}
|
|
|
|
- old_profile = __aa_replace_profile(task, new_profile, 0);
|
|
+ old_profile = __aa_replace_profile(task, new_profile);
|
|
if (IS_ERR(old_profile)) {
|
|
int error;
|
|
|