@@ -413,7 +413,8 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
state = *get_task_state(task);
vsize = eip = esp = 0;
- permitted = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT);
+ permitted = proc_ptrace_may_access_seq(task,
+ PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT, m);
mm = get_task_mm(task);
if (mm) {
vsize = task_vsize(mm);
@@ -430,8 +430,9 @@ static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,
wchan = get_wchan(task);
- if (wchan && ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)
- && !lookup_symbol_name(wchan, symname))
+ if (wchan &&
+ proc_ptrace_may_access_seq(task, PTRACE_MODE_READ_FSCREDS, m) &&
+ !lookup_symbol_name(wchan, symname))
seq_printf(m, "%s", symname);
else
seq_putc(m, '0');
@@ -440,12 +441,12 @@ static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,
}
#endif /* CONFIG_KALLSYMS */
-static int lock_trace(struct task_struct *task)
+static int lock_trace(struct seq_file *m, struct task_struct *task)
{
int err = mutex_lock_killable(&task->signal->cred_guard_mutex);
if (err)
return err;
- if (!ptrace_may_access(task, PTRACE_MODE_ATTACH_FSCREDS)) {
+ if (!proc_ptrace_may_access_seq(task, PTRACE_MODE_ATTACH_FSCREDS, m)) {
mutex_unlock(&task->signal->cred_guard_mutex);
return -EPERM;
}
@@ -478,7 +479,7 @@ static int proc_pid_stack(struct seq_file *m, struct pid_namespace *ns,
trace.entries = entries;
trace.skip = 0;
- err = lock_trace(task);
+ err = lock_trace(m, task);
if (!err) {
save_stack_trace_tsk(task, &trace);
@@ -660,7 +661,7 @@ static int proc_pid_syscall(struct seq_file *m, struct pid_namespace *ns,
unsigned long args[6], sp, pc;
int res;
- res = lock_trace(task);
+ res = lock_trace(m, task);
if (res)
return res;
@@ -770,7 +771,7 @@ static const struct inode_operations proc_def_inode_operations = {
static int proc_single_show(struct seq_file *m, void *v)
{
- struct inode *inode = m->private;
+ struct inode *inode = m->file->f_inode;
struct pid_namespace *ns;
struct pid *pid;
struct task_struct *task;
@@ -790,14 +791,35 @@ static int proc_single_show(struct seq_file *m, void *v)
static int proc_single_open(struct inode *inode, struct file *filp)
{
- return single_open(filp, proc_single_show, inode);
+ int ret;
+ u64 *privunit_id = kmalloc(sizeof(u64), GFP_KERNEL);
+
+ if (privunit_id == NULL)
+ return -ENOMEM;
+ *privunit_id = current->self_privunit_id;
+
+ ret = single_open(filp, proc_single_show, privunit_id);
+
+ if (ret)
+ kfree(privunit_id);
+ return ret;
+}
+
+static int proc_single_release(struct inode *inode, struct file *filp)
+{
+ struct seq_file *seq = filp->private_data;
+ u64 *privunit_id = seq->private;
+
+ kfree(privunit_id);
+
+ return single_release(inode, filp);
}
static const struct file_operations proc_single_file_operations = {
.open = proc_single_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = single_release,
+ .release = proc_single_release,
};
@@ -2157,10 +2179,29 @@ out:
return ret;
}
+int proc_open_get_privunit_id(struct inode *inode, struct file *filp)
+{
+ u64 *privunit_id = kmalloc(sizeof(u64), GFP_KERNEL);
+
+ if (privunit_id == NULL)
+ return -ENOMEM;
+ *privunit_id = current->self_privunit_id;
+ filp->private_data = privunit_id;
+ return 0;
+}
+
+int proc_release_put_privunit_id(struct inode *inode, struct file *filp)
+{
+ kfree((u64 *)filp->private_data);
+ return 0;
+}
+
static const struct file_operations proc_map_files_operations = {
.read = generic_read_dir,
.iterate_shared = proc_map_files_readdir,
.llseek = generic_file_llseek,
+ .open = proc_open_get_privunit_id,
+ .release = proc_release_put_privunit_id,
};
#ifdef CONFIG_CHECKPOINT_RESTORE
@@ -2614,7 +2655,7 @@ static int do_io_accounting(struct task_struct *task, struct seq_file *m, int wh
if (result)
return result;
- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
+ if (!proc_ptrace_may_access_seq(task, PTRACE_MODE_READ_FSCREDS, m)) {
result = -EACCES;
goto out_unlock;
}
@@ -2798,7 +2839,7 @@ static const struct file_operations proc_setgroups_operations = {
static int proc_pid_personality(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
- int err = lock_trace(task);
+ int err = lock_trace(m, task);
if (!err) {
seq_printf(m, "%08x\n", task->personality);
unlock_trace(task);
@@ -14,6 +14,9 @@
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/binfmts.h>
+#include <linux/ptrace.h>
+#include <linux/pid.h>
+#include <linux/seq_file.h>
struct ctl_table_header;
struct mempolicy;
@@ -134,6 +137,15 @@ out:
return ~0U;
}
+static inline bool proc_ptrace_may_access_seq(struct task_struct *task,
+ unsigned int mode, struct seq_file *m)
+{
+ const u64 *privunit_id = m->private;
+
+ return ptrace_may_access_noncurrent(task, mode, m->file->f_cred,
+ privunit_id);
+}
+
/*
* Offset of the first process in the /proc root directory..
*/
@@ -168,6 +180,8 @@ extern int pid_delete_dentry(const struct dentry *);
extern int proc_pid_readdir(struct file *, struct dir_context *);
extern struct dentry *proc_pid_lookup(struct inode *, struct dentry *, unsigned int);
extern loff_t mem_lseek(struct file *, loff_t, int);
+extern int proc_open_get_privunit_id(struct inode *, struct file *);
+extern int proc_release_put_privunit_id(struct inode *, struct file *);
/* Lookups */
typedef int instantiate_t(struct inode *, struct dentry *,
@@ -1147,6 +1147,7 @@
* attributes would be changed by the execve.
* @child contains the task_struct structure for the target process.
* @mode contains the PTRACE_MODE flags indicating the form of access.
+ * @cred contains the caller's credentials
* Return 0 if permission is granted.
* @ptrace_traceme:
* Check that the @parent process has sufficient permission to trace the
@@ -1315,7 +1316,7 @@ union security_list_options {
struct file *file);
int (*ptrace_access_check)(struct task_struct *child,
- unsigned int mode);
+ unsigned int mode, const struct cred *cred);
int (*ptrace_traceme)(struct task_struct *parent);
int (*capget)(struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted);
@@ -66,6 +66,9 @@ extern void exit_ptrace(struct task_struct *tracer, struct list_head *dead);
#define PTRACE_MODE_ATTACH_FSCREDS (PTRACE_MODE_ATTACH | PTRACE_MODE_FSCREDS)
#define PTRACE_MODE_ATTACH_REALCREDS (PTRACE_MODE_ATTACH | PTRACE_MODE_REALCREDS)
+/* ignore relationship between current and target task */
+#define PTRACE_MODE_NON_CURRENT 0x20
+
/**
* ptrace_may_access - check whether the caller is permitted to access
* a target task.
@@ -81,6 +84,8 @@ extern void exit_ptrace(struct task_struct *tracer, struct list_head *dead);
* process_vm_writev or ptrace (and should use the real credentials).
*/
extern bool ptrace_may_access(struct task_struct *task, unsigned int mode);
+bool ptrace_may_access_noncurrent(struct task_struct *task, unsigned int mode,
+ const struct cred *cred, const u64 *privunit_id);
static inline int ptrace_reparented(struct task_struct *child)
{
@@ -72,7 +72,8 @@ struct timezone;
extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
int cap, int audit);
extern int cap_settime(const struct timespec64 *ts, const struct timezone *tz);
-extern int cap_ptrace_access_check(struct task_struct *child, unsigned int mode);
+extern int cap_ptrace_access_check(struct task_struct *child, unsigned int mode,
+ const struct cred *cred);
extern int cap_ptrace_traceme(struct task_struct *parent);
extern int cap_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted);
extern int cap_capset(struct cred *new, const struct cred *old,
@@ -191,7 +192,8 @@ int security_binder_transfer_binder(struct task_struct *from,
struct task_struct *to);
int security_binder_transfer_file(struct task_struct *from,
struct task_struct *to, struct file *file);
-int security_ptrace_access_check(struct task_struct *child, unsigned int mode);
+int security_ptrace_access_check(struct task_struct *child, unsigned int mode,
+ const struct cred *cred);
int security_ptrace_traceme(struct task_struct *parent);
int security_capget(struct task_struct *target,
kernel_cap_t *effective,
@@ -414,9 +416,9 @@ static inline int security_binder_transfer_file(struct task_struct *from,
}
static inline int security_ptrace_access_check(struct task_struct *child,
- unsigned int mode)
+ unsigned int mode, const struct cred *cred)
{
- return cap_ptrace_access_check(child, mode);
+ return cap_ptrace_access_check(child, mode, cred);
}
static inline int security_ptrace_traceme(struct task_struct *parent)
@@ -206,18 +206,20 @@ static int ptrace_check_attach(struct task_struct *child, bool ignore_state)
return ret;
}
-static int ptrace_has_cap(struct user_namespace *ns, unsigned int mode)
+static int ptrace_has_cap(const struct cred *cred, struct user_namespace *ns,
+ unsigned int mode)
{
if (mode & PTRACE_MODE_NOAUDIT)
- return has_ns_capability_noaudit(current, ns, CAP_SYS_PTRACE);
+ return security_capable_noaudit(cred, ns, CAP_SYS_PTRACE) == 0;
else
- return has_ns_capability(current, ns, CAP_SYS_PTRACE);
+ return security_capable(cred, ns, CAP_SYS_PTRACE) == 0;
}
/* Returns 0 on success, -errno on denial. */
-static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
+static int __ptrace_may_access(struct task_struct *task, unsigned int mode,
+ const struct cred *cred, const u64 *privunit_id)
{
- const struct cred *cred = current_cred(), *tcred;
+ const struct cred *tcred;
int dumpable = 0;
kuid_t caller_uid;
kgid_t caller_gid;
@@ -237,8 +239,9 @@ static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
*/
/* Don't let security modules deny introspection */
- if (same_thread_group(task, current))
+ if (*privunit_id == task->self_privunit_id)
return 0;
+
rcu_read_lock();
if (mode & PTRACE_MODE_FSCREDS) {
caller_uid = cred->fsuid;
@@ -263,7 +266,7 @@ static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
gid_eq(caller_gid, tcred->sgid) &&
gid_eq(caller_gid, tcred->gid))
goto ok;
- if (ptrace_has_cap(tcred->user_ns, mode))
+ if (ptrace_has_cap(cred, tcred->user_ns, mode))
goto ok;
rcu_read_unlock();
return -EPERM;
@@ -274,13 +277,13 @@ ok:
dumpable = get_dumpable(task->mm);
rcu_read_lock();
if (dumpable != SUID_DUMP_USER &&
- !ptrace_has_cap(__task_cred(task)->user_ns, mode)) {
+ !ptrace_has_cap(cred, __task_cred(task)->user_ns, mode)) {
rcu_read_unlock();
return -EPERM;
}
rcu_read_unlock();
- return security_ptrace_access_check(task, mode);
+ return security_ptrace_access_check(task, mode, cred);
}
/*
@@ -297,7 +300,21 @@ bool ptrace_may_access(struct task_struct *task, unsigned int mode)
{
int err;
task_lock(task);
- err = __ptrace_may_access(task, mode);
+ err = __ptrace_may_access(task, mode, current_cred(),
+ ¤t->self_privunit_id);
+ task_unlock(task);
+ return !err;
+}
+
+bool ptrace_may_access_noncurrent(struct task_struct *task, unsigned int mode,
+ const struct cred *cred,
+ const u64 *privunit_id)
+{
+ int err;
+
+ task_lock(task);
+ err = __ptrace_may_access(task, mode | PTRACE_MODE_NON_CURRENT, cred,
+ privunit_id);
task_unlock(task);
return !err;
}
@@ -338,7 +355,8 @@ static int ptrace_attach(struct task_struct *task, long request,
goto out;
task_lock(task);
- retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS);
+ retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS,
+ current_cred(), ¤t->self_privunit_id);
task_unlock(task);
if (retval)
goto unlock_creds;
@@ -22,7 +22,7 @@ struct aa_profile;
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
unsigned int mode);
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
+int aa_ptrace(struct aa_profile *tracer_p, struct task_struct *tracee,
unsigned int mode);
#endif /* __AA_IPC_H */
@@ -82,7 +82,7 @@ int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
*
* Returns: %0 else error code if permission denied or error
*/
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
+int aa_ptrace(struct aa_profile *tracer_p, struct task_struct *tracee,
unsigned int mode)
{
/*
@@ -94,7 +94,6 @@ int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
* - tracer profile has CAP_SYS_PTRACE
*/
- struct aa_profile *tracer_p = aa_get_task_profile(tracer);
int error = 0;
if (!unconfined(tracer_p)) {
@@ -105,7 +104,6 @@ int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
aa_put_profile(tracee_p);
}
- aa_put_profile(tracer_p);
return error;
}
@@ -94,14 +94,22 @@ static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
}
static int apparmor_ptrace_access_check(struct task_struct *child,
- unsigned int mode)
+ unsigned int mode, const struct cred *cred)
{
- return aa_ptrace(current, child, mode);
+ struct aa_profile *tracer_p = aa_get_profile(aa_cred_profile(cred));
+ int res = aa_ptrace(tracer_p, child, mode);
+
+ aa_put_profile(tracer_p);
+ return res;
}
static int apparmor_ptrace_traceme(struct task_struct *parent)
{
- return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+ struct aa_profile *tracer_p = aa_get_task_profile(parent);
+ int res = aa_ptrace(tracer_p, current, PTRACE_MODE_ATTACH);
+
+ aa_put_profile(tracer_p);
+ return res;
}
/* Derived from security/commoncap.c:cap_capget */
@@ -133,14 +133,14 @@ int cap_settime(const struct timespec64 *ts, const struct timezone *tz)
* Determine whether a process may access another, returning 0 if permission
* granted, -ve if denied.
*/
-int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
+int cap_ptrace_access_check(struct task_struct *child, unsigned int mode,
+ const struct cred *cred)
{
int ret = 0;
- const struct cred *cred, *child_cred;
+ const struct cred *child_cred;
const kernel_cap_t *caller_caps;
rcu_read_lock();
- cred = current_cred();
child_cred = __task_cred(child);
if (mode & PTRACE_MODE_FSCREDS)
caller_caps = &cred->cap_effective;
@@ -149,7 +149,7 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, *caller_caps))
goto out;
- if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE))
+ if (security_capable(cred, child_cred->user_ns, CAP_SYS_PTRACE) == 0)
goto out;
ret = -EPERM;
out:
@@ -154,9 +154,10 @@ int security_binder_transfer_file(struct task_struct *from,
return call_int_hook(binder_transfer_file, 0, from, to, file);
}
-int security_ptrace_access_check(struct task_struct *child, unsigned int mode)
+int security_ptrace_access_check(struct task_struct *child, unsigned int mode,
+ const struct cred *cred)
{
- return call_int_hook(ptrace_access_check, 0, child, mode);
+ return call_int_hook(ptrace_access_check, 0, child, mode, cred);
}
int security_ptrace_traceme(struct task_struct *parent)
@@ -2133,10 +2133,10 @@ static int selinux_binder_transfer_file(struct task_struct *from,
}
static int selinux_ptrace_access_check(struct task_struct *child,
- unsigned int mode)
+ unsigned int mode, const struct cred *cred)
{
if (mode & PTRACE_MODE_READ) {
- u32 sid = current_sid();
+ u32 sid = cred_sid(cred);
u32 csid = task_sid(child);
return avc_has_perm(sid, csid, SECCLASS_FILE, FILE__READ, NULL);
}
@@ -408,7 +408,8 @@ static inline unsigned int smk_ptrace_mode(unsigned int mode)
/**
* smk_ptrace_rule_check - helper for ptrace access
- * @tracer: tracer process
+ * @tracer: tracer process - only used for auditing if @tracer_cred is set
+ * @tracer_cred: creds for checks, may be different from @tracer's creds
* @tracee_known: label entry of the process that's about to be traced
* @mode: ptrace attachment mode (PTRACE_MODE_*)
* @func: name of the function that called us, used for audit
@@ -416,6 +417,7 @@ static inline unsigned int smk_ptrace_mode(unsigned int mode)
* Returns 0 on access granted, -error on error
*/
static int smk_ptrace_rule_check(struct task_struct *tracer,
+ const struct cred *tracer_cred,
struct smack_known *tracee_known,
unsigned int mode, const char *func)
{
@@ -431,7 +433,9 @@ static int smk_ptrace_rule_check(struct task_struct *tracer,
}
rcu_read_lock();
- tsp = __task_cred(tracer)->security;
+ if (tracer_cred == NULL)
+ tracer_cred = __task_cred(tracer);
+ tsp = tracer_cred->security;
tracer_known = smk_of_task(tsp);
if ((mode & PTRACE_MODE_ATTACH) &&
@@ -476,13 +480,14 @@ static int smk_ptrace_rule_check(struct task_struct *tracer,
*
* Do the capability checks.
*/
-static int smack_ptrace_access_check(struct task_struct *ctp, unsigned int mode)
+static int smack_ptrace_access_check(struct task_struct *ctp, unsigned int mode,
+ const struct cred *cred)
{
struct smack_known *skp;
skp = smk_of_task_struct(ctp);
- return smk_ptrace_rule_check(current, skp, mode, __func__);
+ return smk_ptrace_rule_check(current, cred, skp, mode, __func__);
}
/**
@@ -500,7 +505,8 @@ static int smack_ptrace_traceme(struct task_struct *ptp)
skp = smk_of_task(current_security());
- rc = smk_ptrace_rule_check(ptp, skp, PTRACE_MODE_ATTACH, __func__);
+ rc = smk_ptrace_rule_check(ptp, NULL, skp, PTRACE_MODE_ATTACH,
+ __func__);
return rc;
}
@@ -941,7 +947,7 @@ static int smack_bprm_set_creds(struct linux_binprm *bprm)
rcu_read_lock();
tracer = ptrace_parent(current);
if (likely(tracer != NULL))
- rc = smk_ptrace_rule_check(tracer,
+ rc = smk_ptrace_rule_check(tracer, NULL,
isp->smk_task,
PTRACE_MODE_ATTACH,
__func__);
@@ -347,7 +347,7 @@ static int ptracer_exception_found(struct task_struct *tracer,
* Returns 0 if following the ptrace is allowed, -ve on error.
*/
static int yama_ptrace_access_check(struct task_struct *child,
- unsigned int mode)
+ unsigned int mode, const struct cred *cred)
{
int rc = 0;
@@ -359,7 +359,9 @@ static int yama_ptrace_access_check(struct task_struct *child,
break;
case YAMA_SCOPE_RELATIONAL:
rcu_read_lock();
- if (!task_is_descendant(current, child) &&
+ /* fail open for some procfs-based ATTACH accesses! */
+ if ((mode & PTRACE_MODE_NON_CURRENT) == 0 &&
+ !task_is_descendant(current, child) &&
!ptracer_exception_found(current, child) &&
!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
rc = -EPERM;
@@ -367,7 +369,8 @@ static int yama_ptrace_access_check(struct task_struct *child,
break;
case YAMA_SCOPE_CAPABILITY:
rcu_read_lock();
- if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
+ if (security_capable(cred, __task_cred(child)->user_ns,
+ CAP_SYS_PTRACE) != 0)
rc = -EPERM;
rcu_read_unlock();
break;
This adds a new ptrace_may_access_noncurrent() method that uses the supplied credentials instead of those of the current task. (However, the current task may still be inspected for auditing purposes, e.g. by the Smack LSM.) procfs used the caller's creds for a few ptrace_may_access() checks at read() time, which made a confused deputy attack by passing an FD to a procfs file to a setuid program possible. Therefore, the following was a local userspace ASLR bypass: rm -f /tmp/foobar procmail <(echo 'DEFAULT=/tmp/foobar') </proc/1/stat ( echo 'obase=16' cut -d' ' -f26-30,45-51 </tmp/foobar | tr ' ' '\n' ) | bc rm /tmp/foobar procmail is installed setuid root on Debian and read()s data from stdin before dropping privs, so the ptrace_may_access() check in the VFS read handler of /proc/1/stat passes. Procmail then dumps the read data to a user-accessible file (/tmp/foobar here). Signed-off-by: Jann Horn <jann@thejh.net> --- fs/proc/array.c | 3 +- fs/proc/base.c | 63 ++++++++++++++++++++++++++++++++++------- fs/proc/internal.h | 14 +++++++++ include/linux/lsm_hooks.h | 3 +- include/linux/ptrace.h | 5 ++++ include/linux/security.h | 10 ++++--- kernel/ptrace.c | 40 +++++++++++++++++++------- security/apparmor/include/ipc.h | 2 +- security/apparmor/ipc.c | 4 +-- security/apparmor/lsm.c | 14 +++++++-- security/commoncap.c | 8 +++--- security/security.c | 5 ++-- security/selinux/hooks.c | 4 +-- security/smack/smack_lsm.c | 18 ++++++++---- security/yama/yama_lsm.c | 9 ++++-- 15 files changed, 150 insertions(+), 52 deletions(-)