Skip to contentSkip to conversation

Yama: add PTRACE exception tracking

PATCH In review Kees Cook 7 participants 20 messages Jun 30, 2010 – Jul 1, 2010

Subsystems (5)
lore
Share & tools
lore

Kees Cook

author
#0 lore raw

cover Yama: add PTRACE exception tracking

The primary exception to Yama's descendant-based PTRACE restrictions is when an application has a predefined crash handler that is spawned in parallel with the crashed application (e.g. KDE, Chromium). These applications want to bypass the common RLIMIT_CORE=0, and gather state information from the process for upstream problem reporting. When the main application crashes, it generally has access to the PID of what will debug it (e.g. when a KDE app crashes, it knows the parent PID of the debugger that will be spawned).

So, since this programmatic method of PTRACEing is useful, there should be a way for processes to actively declare who can PTRACE them. This patch adds a prctl hook for Yama so that processes can exempt themselves from the PTRACE restrictions in the case of a crash when they know their debugger's PID.

As a matter of demonstration, here is what the patch to KDE4 would look like to support Yama, or other PTRACE-restricting LSMs that wanted to grant a similar exception:

kdeui/util/kcrash.cpp +2 -0
@@ -41,6 +41,7 @@
 #include <sys/wait.h>
 #include <sys/un.h>
 #include <sys/socket.h>
+#include <sys/prctl.h>
 #include <errno.h>
 
 #include <qwindowdefs.h>
@@ -437,6 +438,7 @@
             //if the process was started directly, use waitpid(), as it's a child...
             while(waitpid(-1, NULL, 0) != pid) {}
         } else {
+            prctl(PR_SET_PTRACER, pid, 0, 0, 0);
             //...else poll its status using kill()
             while(kill(pid, 0) >= 0) {
                 sleep(1);
Signature · 2 lines
Kees Cook
Ubuntu Security Team
author
#1 lore raw

PATCH 1/2 security: create task_free security callback

The current LSM interface to cred_free is not sufficient for allowing an LSM to track the life and death of a task. This patch adds the task_free hook so that an LSM can clean up resources on task death.

---
 include/linux/security.h |    8 ++++++++
 kernel/fork.c            |    1 +
 security/capability.c    |    4 ++++
 security/security.c      |    5 +++++
 4 files changed, 18 insertions(+), 0 deletions(-)
include/linux/security.h +8 -0
index 723a93d..8007495 100644
@@ -637,6 +637,9 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
  *	manual page for definitions of the @clone_flags.
  *	@clone_flags contains the flags indicating what should be shared.
  *	Return 0 if permission is granted.
+ * @task_free:
+ *	@task task being freed
+ *	Handle release of task-related resources.
  * @cred_alloc_blank:
  *	@cred points to the credentials.
  *	@gfp indicates the atomicity of any memory allocations.
@@ -1481,6 +1484,7 @@ struct security_operations {
 	int (*dentry_open) (struct file *file, const struct cred *cred);
 
 	int (*task_create) (unsigned long clone_flags);
+	void (*task_free) (struct task_struct *task);
 	int (*cred_alloc_blank) (struct cred *cred, gfp_t gfp);
 	void (*cred_free) (struct cred *cred);
 	int (*cred_prepare)(struct cred *new, const struct cred *old,
@@ -1732,6 +1736,7 @@ int security_file_send_sigiotask(struct task_struct *tsk,
 int security_file_receive(struct file *file);
 int security_dentry_open(struct file *file, const struct cred *cred);
 int security_task_create(unsigned long clone_flags);
+void security_task_free(struct task_struct *task);
 int security_cred_alloc_blank(struct cred *cred, gfp_t gfp);
 void security_cred_free(struct cred *cred);
 int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp);
@@ -2232,6 +2237,9 @@ static inline int security_task_create(unsigned long clone_flags)
 	return 0;
 }
 
+static inline int security_task_free(struct task_struct *task)
+{ }
+
 static inline int security_cred_alloc_blank(struct cred *cred, gfp_t gfp)
 {
 	return 0;
kernel/fork.c +1 -0
index b6cce14..7fbc5e3 100644
@@ -183,6 +183,7 @@ void __put_task_struct(struct task_struct *tsk)
 	WARN_ON(atomic_read(&tsk->usage));
 	WARN_ON(tsk == current);
 
+	security_task_free(tsk);
 	exit_creds(tsk);
 	delayacct_tsk_free(tsk);
 	put_signal_struct(tsk->signal);
security/capability.c +4 -0
index 4aeb699..3649d4b 100644
@@ -353,6 +353,9 @@ static int cap_task_create(unsigned long clone_flags)
 	return 0;
 }
 
+static void cap_task_free(struct task_struct *task)
+{ }
+
 static int cap_cred_alloc_blank(struct cred *cred, gfp_t gfp)
 {
 	return 0;
@@ -936,6 +939,7 @@ void __init security_fixup_ops(struct security_operations *ops)
 	set_to_cap_if_null(ops, file_receive);
 	set_to_cap_if_null(ops, dentry_open);
 	set_to_cap_if_null(ops, task_create);
+	set_to_cap_if_null(ops, task_free);
 	set_to_cap_if_null(ops, cred_alloc_blank);
 	set_to_cap_if_null(ops, cred_free);
 	set_to_cap_if_null(ops, cred_prepare);
security/security.c +5 -0
index e8c87b8..103c35f 100644
@@ -691,6 +691,11 @@ int security_task_create(unsigned long clone_flags)
 	return security_ops->task_create(clone_flags);
 }
 
+void security_task_free(struct task_struct *task)
+{
+	security_ops->task_free(task);
+}
+
 int security_cred_alloc_blank(struct cred *cred, gfp_t gfp)
 {
 	return security_ops->cred_alloc_blank(cred, gfp);
Signature · 6 lines
1.7.1


-- 
Kees Cook
Ubuntu Security Team
author
#2 lore raw

PATCH 2/2 Yama: add PTRACE exception tracking

In reply to Kees Cook

Some application suites have external crash handlers that depend on being able to use PTRACE to generate crash reports (KDE, Chromium, etc). Since the inferior process generally knows the PID of the debugger, it can use PR_SET_PTRACER to allow a specific PID and its descendants to perform the PTRACE instead of only a direct ancestor.

---
 include/linux/prctl.h    |    6 ++
 security/yama/yama_lsm.c |  205 ++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 193 insertions(+), 18 deletions(-)
include/linux/prctl.h +6 -0
index a3baeb2..e435624 100644
@@ -102,4 +102,10 @@
 
 #define PR_MCE_KILL_GET 34
 
+/*
+ * Set specific pid that is allowed to PTRACE the current task.
+ * A value of 0 mean "no process".
+ */
+#define PR_SET_PTRACER 35
+
 #endif /* _LINUX_PRCTL_H */
security/yama/yama_lsm.c +187 -18

241 lines not on this page · raw message

Signature · 6 lines
1.7.1


-- 
Kees Cook
Ubuntu Security Team
#3 lore raw
Quoted 1 line · Kees Cook

+static spinlock_t ptracer_relations_lock;

static DEFINE_SPINLOCK(ptracer_relations_lock);

security/yama/yama_lsm.c line 46

Quoted 6 lines
security/yama/yama_lsm.c +6 -0
+static int yama_ptracer_add(struct task_struct *tracer,
+			    struct task_struct *tracee)
+{
+	struct ptrace_relation *relation;
+
+	relation = kzalloc(sizeof(*relation), GFP_KERNEL);

You can use kmalloc() since all fields are initialized within this function.

Quoted 17 lines
security/yama/yama_lsm.c +16 -0
+	if (!relation)
+		return -ENOMEM;
+	relation->tracer = tracer;
+	relation->tracee = tracee;
+	spin_lock(&ptracer_relations_lock);
+	list_add(&relation->node, &ptracer_relations);
+	spin_unlock(&ptracer_relations_lock);
+
+	return 0;
+}
 
+static int ptracer_exception_found(struct task_struct *tracer,
+				   struct task_struct *tracee)
+{
+	int rc = 0;
+	struct ptrace_relation *relation;
+	struct task_struct *parent = NULL;

security/yama/yama_lsm.c line 190

Quoted 10 lines
security/yama/yama_lsm.c +10 -0
+
+	spin_lock(&ptracer_relations_lock);
+	list_for_each_entry(relation, &ptracer_relations, node)
+		if (relation->tracee == tracee) {
+			parent = relation->tracer;
+			break;
+		}
+	if (task_is_descendant(parent, tracer))
+		rc = 1;
+	spin_unlock(&ptracer_relations_lock);

Can't we release ptracer_relations_lock before calling task_is_descendant() since task_is_descendant() won't access "struct ptrace_relation" on ptracer_relations list.

security/yama/yama_lsm.c static int yama_ptrace_access_check(struct task_struct *child, · line 208

Quoted 6 lines
security/yama/yama_lsm.c +2 -0
@@ -32,27 +204,20 @@ static int yama_ptrace_access_check(struct task_struct *child,
 {
 	int rc;
 
+	/* If standard caps disallows it, so does Yama.  We should
+	 * should only tighten restrictions further.

s/should should/should/

security/yama/yama_lsm.c static __init int yama_init(void) · line 392

Quoted 8 lines
security/yama/yama_lsm.c +3 -0
+	 */
 
@@ -221,6 +388,8 @@ static __init int yama_init(void)
 
 	printk(KERN_INFO "Yama: becoming mindful.\n");
 
+	spin_lock_init(&ptracer_relations_lock);
+

You can statically initialize by using DEFINE_SPINLOCK().

author
#4 lore raw

Hi Tetsuo,

Quoted 3 lines · Tetsuo Handa
Quoted 1 line · Kees Cook

+static spinlock_t ptracer_relations_lock;

static DEFINE_SPINLOCK(ptracer_relations_lock);

Ah, very cool, I missed that while reading through spinlock code. :)

Quoted 2 lines

security/yama/yama_lsm.c line 46

Quoted 1 line
security/yama/yama_lsm.c +1 -0
+	relation = kzalloc(sizeof(*relation), GFP_KERNEL);

You can use kmalloc() since all fields are initialized within this function.

I wasn't sure if list_add needed a zeroed ->node, so I opted for safety here. Is list_add safe to use on an uninitialized ->node? (Looks like it is on code review, I'll just use regular kmalloc.)

Quoted 12 lines
Quoted 12 lines
security/yama/yama_lsm.c +12 -0
+static int ptracer_exception_found(struct task_struct *tracer,
+				   struct task_struct *tracee)
+{
+	int rc = 0;
+	struct ptrace_relation *relation;
+	struct task_struct *parent = NULL;
+
+	spin_lock(&ptracer_relations_lock);
+	list_for_each_entry(relation, &ptracer_relations, node)
+		if (relation->tracee == tracee) {
+			parent = relation->tracer;
+			break;
Quoted 8 lines

security/yama/yama_lsm.c line 190

Quoted 4 lines
security/yama/yama_lsm.c +4 -0
+		}
+	if (task_is_descendant(parent, tracer))
+		rc = 1;
+	spin_unlock(&ptracer_relations_lock);

Can't we release ptracer_relations_lock before calling task_is_descendant() since task_is_descendant() won't access "struct ptrace_relation" on ptracer_relations list.

This is where it gets a little funny. I need to keep that lock so that task_is_descendant isn't racing yama_task_free. I don't want to be in the position where I've left the lock only to have another CPU free the task_struct that was just located, so I have to keep the lock until I've finished using "parent". (And I can't take the task with get_task since it's already too late, and if I take it during _add, the task will never be freed.)

Quoted 7 lines

security/yama/yama_lsm.c static int yama_ptrace_access_check(struct task_struct *child, · line 208

Quoted 6 lines
security/yama/yama_lsm.c +2 -0
@@ -32,27 +204,20 @@ static int yama_ptrace_access_check(struct task_struct *child,
 {
 	int rc;
 
+	/* If standard caps disallows it, so does Yama.  We should
+	 * should only tighten restrictions further.

s/should should/should/

Agh, thanks.

-Kees

Signature · 2 lines
Kees Cook
Ubuntu Security Team
maintainer
#5 lore raw
In reply to Kees Cook
Quoted 8 lines · Kees Cook

Some application suites have external crash handlers that depend on being able to use PTRACE to generate crash reports (KDE, Chromium, etc). Since the inferior process generally knows the PID of the debugger, it can use PR_SET_PTRACER to allow a specific PID and its descendants to perform the PTRACE instead of only a direct ancestor.

---

Hi Kees - very nice, overall. One little note though:

security/yama/yama_lsm.c static int yama_ptrace_access_check(struct task_struct *child, · line 210

Quoted 8 lines
security/yama/yama_lsm.c +3 -0
@@ -32,27 +204,20 @@ static int yama_ptrace_access_check(struct task_struct *child,
 {
 	int rc;
 
+	/* If standard caps disallows it, so does Yama.  We should
+	 * should only tighten restrictions further.
+	 */
 	rc = cap_ptrace_access_check(child, mode);

This means that if capable(CAP_SYS_PTRACE) we'll always shortcut here, so

Quoted 14 lines
security/yama/yama_lsm.c +1 -10
-	if (rc != 0)
+	if (rc)
 		return rc;
 
 	/* require ptrace target be a child of ptracer on attach */
-	if (mode == PTRACE_MODE_ATTACH && ptrace_scope &&
-	    !capable(CAP_SYS_PTRACE)) {
-		struct task_struct *walker = child;
-
-		rcu_read_lock();
-		read_lock(&tasklist_lock);
-		while (walker->pid > 0) {
-			if (walker == current)
-				break;

security/yama/yama_lsm.c static int yama_ptrace_access_check(struct task_struct *child, · line 217

Quoted 10 lines
security/yama/yama_lsm.c +3 -7
-			walker = walker->real_parent;
-		}
-		if (walker->pid == 0)
-			rc = -EPERM;
-		read_unlock(&tasklist_lock);
-		rcu_read_unlock();
-	}
+	if (mode == PTRACE_MODE_ATTACH &&
+	    ptrace_scope &&
+	    !capable(CAP_SYS_PTRACE) &&

You don't need the CAP_SYS_PTRACE check here AFAICS.

Quoted 35 lines
security/yama/yama_lsm.c +7 -0
+	    !task_is_descendant(current, child) &&
+	    !ptracer_exception_found(current, child))
+		rc = -EPERM;
 
 	if (rc) {
 		char name[sizeof(current->comm)];
@@ -170,6 +335,8 @@ static struct security_operations yama_ops = {
 	.ptrace_access_check =	yama_ptrace_access_check,
 	.inode_follow_link =	yama_inode_follow_link,
 	.path_link =		yama_path_link,
+	.task_prctl =		yama_task_prctl,
+	.task_free =		yama_task_free,
 };
 
 #ifdef CONFIG_SYSCTL
@@ -221,6 +388,8 @@ static __init int yama_init(void)
 
 	printk(KERN_INFO "Yama: becoming mindful.\n");
 
+	spin_lock_init(&ptracer_relations_lock);
+
 	if (register_security(&yama_ops))
 		panic("Yama: kernel registration failed.\n");
 
Signature · 10 lines
1.7.1


-- 
Kees Cook
Ubuntu Security Team
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
author
#6 lore raw

Hi Serge,

Quoted 11 lines · Serge E. Hallyn
Quoted 8 lines · Kees Cook

Some application suites have external crash handlers that depend on being able to use PTRACE to generate crash reports (KDE, Chromium, etc). Since the inferior process generally knows the PID of the debugger, it can use PR_SET_PTRACER to allow a specific PID and its descendants to perform the PTRACE instead of only a direct ancestor.

---

Hi Kees - very nice, overall. One little note though:

Thanks for looking it over!

Quoted 5 lines
Quoted 1 line

rc = cap_ptrace_access_check(child, mode);

This means that if capable(CAP_SYS_PTRACE) we'll always shortcut here, so

Quoted 8 lines

security/yama/yama_lsm.c static int yama_ptrace_access_check(struct task_struct *child, · line 220

Quoted 6 lines
security/yama/yama_lsm.c +6 -0
+	if (mode == PTRACE_MODE_ATTACH &&
+	    ptrace_scope &&
+	    !capable(CAP_SYS_PTRACE) &&
+	    !task_is_descendant(current, child) &&
+	    !ptracer_exception_found(current, child))
+		rc = -EPERM;

You don't need the CAP_SYS_PTRACE check here AFAICS.

I don't think that's true -- the capable(CAP_SYS_PTRACE) tests are always done in the negative since we only ever abort with error instead of forcing an early "okay" (see also __ptrace_may_access() in kernel/ptrace.c, where capable(CAP_SYS_PTRACE) is called repeatedly while evaluating various negative conditions).

For cap_ptrace_access_check, capable(CAP_SYS_PTRACE) is only tested if the tracee's process permitted caps are not a subset of the tracer's. i.e. cap_ptrace_access_check will return 0 when either cap_issubset or capable. In the case of normal user processes or a tracer with greater caps, capable(CAP_SYS_PTRACE) will never be tested in cap_ptrace_access_check.

As a result, I have to include it in the test here too. I guess it's arguable that I should move it to the end of the series of &&s, but it logically doesn't really matter.

-Kees

(Interestingly, this means that having CAP_SYS_PTRACE means a process effectively has all capabilities...)

Signature · 2 lines
Kees Cook
Ubuntu Security Team
maintainer
#7 lore raw
Quoted 31 lines · Kees Cook

Hi Serge,

Quoted 11 lines · Serge E. Hallyn
Quoted 8 lines · Kees Cook

Some application suites have external crash handlers that depend on being able to use PTRACE to generate crash reports (KDE, Chromium, etc). Since the inferior process generally knows the PID of the debugger, it can use PR_SET_PTRACER to allow a specific PID and its descendants to perform the PTRACE instead of only a direct ancestor.

---

Hi Kees - very nice, overall. One little note though:

Thanks for looking it over!

Quoted 5 lines
Quoted 1 line

rc = cap_ptrace_access_check(child, mode);

This means that if capable(CAP_SYS_PTRACE) we'll always shortcut here, so

Quoted 8 lines

security/yama/yama_lsm.c static int yama_ptrace_access_check(struct task_struct *child, · line 220

Quoted 6 lines
security/yama/yama_lsm.c +6 -0
+	if (mode == PTRACE_MODE_ATTACH &&
+	    ptrace_scope &&
+	    !capable(CAP_SYS_PTRACE) &&
+	    !task_is_descendant(current, child) &&
+	    !ptracer_exception_found(current, child))
+		rc = -EPERM;

You don't need the CAP_SYS_PTRACE check here AFAICS.

Quoted 2 lines

I don't think that's true -- the capable(CAP_SYS_PTRACE) tests are always done in the negative since we only ever abort with error

Haha, you're right, I looked at that wrong :)

-serge

#8 lore raw
In reply to Kees Cook
Quoted 7 lines · Kees Cook

Some application suites have external crash handlers that depend on being able to use PTRACE to generate crash reports (KDE, Chromium, etc). Since the inferior process generally knows the PID of the debugger, it can use PR_SET_PTRACER to allow a specific PID and its descendants to perform the PTRACE instead of only a direct ancestor.

any normal unpriv application:

while(1) {
        prctl(PR_SET_PTRACER, 1, 0, 0, 0);
}

watch kernel run out of memory and bring down the box. Seems like quite the DoS.....

-Eric

author
#9 lore raw

Hi Eric,

Quoted 9 lines · Eric Paris
Quoted 7 lines · Kees Cook

Some application suites have external crash handlers that depend on being able to use PTRACE to generate crash reports (KDE, Chromium, etc). Since the inferior process generally knows the PID of the debugger, it can use PR_SET_PTRACER to allow a specific PID and its descendants to perform the PTRACE instead of only a direct ancestor.

Quoted 8 lines

any normal unpriv application:

while(1) {
        prctl(PR_SET_PTRACER, 1, 0, 0, 0);
}

watch kernel run out of memory and bring down the box. Seems like quite the DoS.....

Yes, thanks for noticing this; it seems the version I sent did not include the fixes I made at some point to correctly replace exceptions:

security/yama/yama_lsm.c +21 -9
index f24b6b3..4f160db 100644
@@ -32,7 +32,7 @@ static LIST_HEAD(ptracer_relations);
 static DEFINE_SPINLOCK(ptracer_relations_lock);
 
 /**
- * yama_ptracer_add - add an exception for this tracer/tracee pair
+ * yama_ptracer_add - add/replace an exception for this tracer/tracee pair
  * @tracer: the task_struct of the process doing the PTRACE
  * @tracee: the task_struct of the process to be PTRACEd
  *
@@ -41,18 +41,30 @@ static DEFINE_SPINLOCK(ptracer_relations_lock);
 static int yama_ptracer_add(struct task_struct *tracer,
 			    struct task_struct *tracee)
 {
-	struct ptrace_relation *relation;
+	int rc = 0;
+	struct ptrace_relation *entry, *relation = NULL;
 
-	relation = kmalloc(sizeof(*relation), GFP_KERNEL);
-	if (!relation)
-		return -ENOMEM;
-	relation->tracer = tracer;
-	relation->tracee = tracee;
 	spin_lock(&ptracer_relations_lock);
-	list_add(&relation->node, &ptracer_relations);
+	list_for_each_entry(entry, &ptracer_relations, node)
+		if (entry->tracee == tracee) {
+			relation = entry;
+			break;
+		}
+	if (!relation) {
+		relation = kmalloc(sizeof(*relation), GFP_KERNEL);
+		if (!relation) {
+			rc = -ENOMEM;
+			goto unlock_out;
+		}
+		relation->tracee = tracee;
+		list_add(&relation->node, &ptracer_relations);
+	}
+	relation->tracer = tracer;
+
+unlock_out:
 	spin_unlock(&ptracer_relations_lock);
 
-	return 0;
+	return rc;
 }
 
 /**

-Kees

Signature · 2 lines
Kees Cook
Ubuntu Security Team
#10 lore raw
Quoted 12 lines · Kees Cook
security/yama/yama_lsm.c +2 -5
@@ -41,18 +41,30 @@ static DEFINE_SPINLOCK(ptracer_relations_lock);
 static int yama_ptracer_add(struct task_struct *tracer,
 			    struct task_struct *tracee)
 {
-	struct ptrace_relation *relation;
+	int rc = 0;
+	struct ptrace_relation *entry, *relation = NULL;
 
-	relation = kmalloc(sizeof(*relation), GFP_KERNEL);
-	if (!relation)
-		return -ENOMEM;
-	relation->tracer = tracer;

security/yama/yama_lsm.c static DEFINE_SPINLOCK(ptracer_relations_lock); · line 54

Quoted 10 lines
security/yama/yama_lsm.c +7 -2
-	relation->tracee = tracee;
 	spin_lock(&ptracer_relations_lock);
-	list_add(&relation->node, &ptracer_relations);
+	list_for_each_entry(entry, &ptracer_relations, node)
+		if (entry->tracee == tracee) {
+			relation = entry;
+			break;
+		}
+	if (!relation) {
+		relation = kmalloc(sizeof(*relation), GFP_KERNEL);

You can't use GFP_KERNEL here because a spinlock is held.

Quoted 15 lines
security/yama/yama_lsm.c +11 -1
+		if (!relation) {
+			rc = -ENOMEM;
+			goto unlock_out;
+		}
+		relation->tracee = tracee;
+		list_add(&relation->node, &ptracer_relations);
+	}
+	relation->tracer = tracer;
+
+unlock_out:
 	spin_unlock(&ptracer_relations_lock);
 
-	return 0;
+	return rc;
 }
#11 lore raw
In reply to Kees Cook

Err, no. This is just a very clear sign that your ptrace restrictions were completely wrong to start with and break applications left, right and center. Just get rid of it instead of letting workarounds for your bad design creep into the core kernel and applications.

author
#12 lore raw

Hi Christoph,

Quoted 4 lines · Christoph Hellwig

Err, no. This is just a very clear sign that your ptrace restrictions were completely wrong to start with and break applications left, right and center. Just get rid of it instead of letting workarounds for your bad design creep into the core kernel and applications.

It's not my bad design; PTRACE is a terrible interface. In an effort to eliminate PTRACE, there are a few legitimate uses: direct debugging, and crash handlers. The crash handlers are an odd case because all they want is a backtrace and register details, but there's no way to do that on the fly without PTRACE, so that's how they've implemented it. In those cases, the crashing program knows who will attach to it, so there needs to be a safe way to declare that relationship instead of just giving up and saying "oh well, everything can PTRACE everything else".

What is so objectionable about using a single PR_* value out of the 2147483614 available?

-Kees

Signature · 2 lines
Kees Cook
Ubuntu Security Team
maintainer
#13 lore raw
In reply to Christoph Hellwig
Quoted 4 lines · Christoph Hellwig

Err, no. This is just a very clear sign that your ptrace restrictions were completely wrong to start with and break applications left, right and center. Just get rid of it instead of letting workarounds for your bad design creep into the core kernel and applications.

Indeed, I wasn't aware that there were further aspects to this -- I thought it was a relatively simple case of restricting a problematic OS feature for heavily locked down systems.

This is getting more complicated, with fine-grained security policy now being introduced, also with the need to modify applications.

There are several existing LSMs with the ability to control ptrace, but as part of a system-wide, coherent, analyzable policy -- often in support of specific security models for which there is concrete user demand and benefit.

If people won't use any of SELinux, Smack, Tomoyo or AppArmor, then I don't think providing an ad-hoc assortment of workarounds with no overall design is going to help them either.

If LSMs need to call into common code in Yama, or even do lightweight chaining, that's one thing, but for Yama to evolve into yet another standalone security scheme, is something entirely different.

- James

Signature · 2 lines
James Morris
<[email protected]>
author
#14 lore raw

Hi James,

Quoted 9 lines · James Morris
Quoted 4 lines · Christoph Hellwig

Err, no. This is just a very clear sign that your ptrace restrictions were completely wrong to start with and break applications left, right and center. Just get rid of it instead of letting workarounds for your bad design creep into the core kernel and applications.

Indeed, I wasn't aware that there were further aspects to this -- I thought it was a relatively simple case of restricting a problematic OS feature for heavily locked down systems.

That's certainly my intent, but PTRACE is kind of a nasty beast.

Quoted 2 lines

This is getting more complicated, with fine-grained security policy now being introduced, also with the need to modify applications.

Well, I'm trying to solve what I think is a core problem with PTRACE -- it is too permissive. I'm happy to look at it from other angles if it doesn't make sense for this kind of thing to live in Yama. I'm already very happy with the existing restrictions available in Yama.

Quoted 4 lines

There are several existing LSMs with the ability to control ptrace, but as part of a system-wide, coherent, analyzable policy -- often in support of specific security models for which there is concrete user demand and benefit.

Sure. I am curious, though, is there a way for SELinux (or maybe Smack, since it has more dynamic labels) to declare this kind of on-runtime PTRACE relationship? Maybe I overlooked some options for this. I didn't see any way for the kernel to intuit the relationship without the tracee specifically declaring which process could PTRACE it, though. Regardless of the method, I think userspace changes would be needed for this kind of thing. I spent a little time discussing this within Ubuntu[1].

Quoted 3 lines

If LSMs need to call into common code in Yama, or even do lightweight chaining, that's one thing, but for Yama to evolve into yet another standalone security scheme, is something entirely different.

I still think simple chaining is the way to go. I want to review the earlier discussions first (I think Serge said it was in 2004ish?) before I write up anything. There is, I think, one sticking point, which is /proc/self/attr/current, but beyond that, I think some simple reorganization of LSM initialization routines and a list that security_* walks would be sufficient.

Thanks,

-Kees

[1] https://lists.ubuntu.com/archives/ubuntu-devel/2010-June/030939.html

Signature · 2 lines
Kees Cook
Ubuntu Security Team
maintainer
#15 lore raw
Quoted 7 lines · Kees Cook
Quoted 2 lines

This is getting more complicated, with fine-grained security policy now being introduced, also with the need to modify applications.

Well, I'm trying to solve what I think is a core problem with PTRACE -- it is too permissive. I'm happy to look at it from other angles if it doesn't make sense for this kind of thing to live in Yama. I'm already very happy with the existing restrictions available in Yama.

I've been jumping from one conviction to another to yet another and back again on this.

First off, if you consider PTRACE_PTRACEME, and just consider this a more finegrained targeted version of that, it doesn't seem all that gross. So maybe that's my fault for suggesting prctl as an easier-to-use in LSMs api, bc using a PTRACE_PTRACEDBY might just look cleaner.

Still, you say 'ptrace is too permissive', but a rebuttal to that is that, in a DAC system, ptrace uses what credentials it knows of to authorize. *You* can make it more finegrained by not insisting on running everything as a single user.

What you now are trying to do is find a new, natural relationship between tasks on a plain DAC system to provide finer-grained control. The one you picked - process ancestry - doesn't perfectly fit, so you add changes and make it less clean. The criticism of that is valid and needs to be discusssed.

Adding new relationships between tasks is what LSMs do - based on the policy-defined relationships between the security tasks of the respective domains. And it feeld natural there - so it's natural for SELinux and apparmor to confine firefox to a domain that can't ptrace anything else (and maybe not itself).

One q then is whether YAMA wants to provide task tracking of its own, or stack with apparmor.

Quoted 8 lines
Quoted 4 lines

There are several existing LSMs with the ability to control ptrace, but as part of a system-wide, coherent, analyzable policy -- often in support of specific security models for which there is concrete user demand and benefit.

Sure. I am curious, though, is there a way for SELinux (or maybe Smack, since it has more dynamic labels) to declare this kind of on-runtime PTRACE relationship? Maybe I overlooked some options for this. I didn't see any

In SELinux, you could give a debugger or crash handler an unprivileged, but allowed-to-ptrace-the-main-app domain.

Quoted 6 lines

I still think simple chaining is the way to go. I want to review the earlier discussions first (I think Serge said it was in 2004ish?) before I write up anything. There is, I think, one sticking point, which is /proc/self/attr/current, but beyond that, I think some simple reorganization of LSM initialization routines and a list that security_* walks would be sufficient.

In the past, output results for each LSM were simply split by \n or a :
or something, and input was prepended by the LSM name.

-serge

#16 lore raw
Quoted 4 lines · Serge E. Hallyn

First off, if you consider PTRACE_PTRACEME, and just consider this a more finegrained targeted version of that, it doesn't seem all that gross. So maybe that's my fault for suggesting prctl as an easier-to-use in LSMs api, bc using a PTRACE_PTRACEDBY might just look cleaner.

TRACEME is an alternative mechanism (to ATTACH) for establishing a tracing relationship, not a mechanism for expressing policy over ATTACH requests. The prctl was solely for configuring (Yama-specific) policy.

Quoted 11 lines

Still, you say 'ptrace is too permissive', but a rebuttal to that is that, in a DAC system, ptrace uses what credentials it knows of to authorize. *You* can make it more finegrained by not insisting on running everything as a single user.

What you now are trying to do is find a new, natural relationship between tasks on a plain DAC system to provide finer-grained control. The one you picked - process ancestry - doesn't perfectly fit, so you add changes and make it less clean. The criticism of that is valid and needs to be discusssed.

Quoted 5 lines

Adding new relationships between tasks is what LSMs do - based on the policy-defined relationships between the security tasks of the respective domains. And it feeld natural there - so it's natural for SELinux and apparmor to confine firefox to a domain that can't ptrace anything else (and maybe not itself).

Or to express whatever ptrace relationships you want to allow, regardless of process ancestry.

Quoted 2 lines

One q then is whether YAMA wants to provide task tracking of its own, or stack with apparmor.

Last I looked, apparmor did not support any kind of fine-grained ptrace constraints, just a simple unconfined || same-profile || CAP_SYS_PTRACE check. Whereas SELinux lets you control it based on the particular domain pairing.

Quoted 11 lines
Quoted 8 lines
Quoted 4 lines

There are several existing LSMs with the ability to control ptrace, but as part of a system-wide, coherent, analyzable policy -- often in support of specific security models for which there is concrete user demand and benefit.

Sure. I am curious, though, is there a way for SELinux (or maybe Smack, since it has more dynamic labels) to declare this kind of on-runtime PTRACE relationship? Maybe I overlooked some options for this. I didn't see any

In SELinux, you could give a debugger or crash handler an unprivileged, but allowed-to-ptrace-the-main-app domain.

Exactly. What we do not want to do is to have the applications configuring policy on the fly.

Quoted 9 lines
Quoted 6 lines

I still think simple chaining is the way to go. I want to review the earlier discussions first (I think Serge said it was in 2004ish?) before I write up anything. There is, I think, one sticking point, which is /proc/self/attr/current, but beyond that, I think some simple reorganization of LSM initialization routines and a list that security_* walks would be sufficient.

In the past, output results for each LSM were simply split by \n or a :
or something, and input was prepended by the LSM name.

I think the final form of the stacker patches put each value on its own line with the module name in parentheses after it. But it required a compatibility hack for SELinux and ultimately I think a libselinux patch to support SELinux stacked with anything else that wanted to use /proc/self/attr.

Signature · 2 lines
Stephen Smalley
National Security Agency
author
#17 lore raw
In reply to Serge E. Hallyn
Quoted 4 lines · Serge E. Hallyn

First off, if you consider PTRACE_PTRACEME, and just consider this a more finegrained targeted version of that, it doesn't seem all that gross. So maybe that's my fault for suggesting prctl as an easier-to-use in LSMs api, bc using a PTRACE_PTRACEDBY might just look cleaner.

Right, this was my thinking -- there is already one kind of declared relationship via TRACEME (though it's utility is for "pure" debugging). The other "regular" use of PTRACE is crash handlers, for which this is no declared relationship. (If you ignore simple DAC, of course.) The third PTRACE use is "arbitrary" debugging -- sysadmins or the like saying "wtf is that process DOING?"

When thinking about the PTRACE stuff originally, I hadn't realized the "crash handler" case. So "pure" was done via TRACEME, and "arbitrary" was done via CAP_SYS_PTRACE, but there wasn't a clear way to declare the "crash" case.

Quoted 10 lines

Still, you say 'ptrace is too permissive', but a rebuttal to that is that, in a DAC system, ptrace uses what credentials it knows of to authorize. *You* can make it more finegrained by not insisting on running everything as a single user.

What you now are trying to do is find a new, natural relationship between tasks on a plain DAC system to provide finer-grained control. The one you picked - process ancestry - doesn't perfectly fit, so you add changes and make it less clean. The criticism of that is valid and needs to be discusssed.

Actually, if you throw out process ancestry completely, and just use TRACEME and TRACEBY, everything still works. The idea would be to just toss out the old definition of DAC PTRACE permissions.

Quoted 2 lines

One q then is whether YAMA wants to provide task tracking of its own, or stack with apparmor.

This is why I asked the question below... I don't want to reinvent the wheel, but from what I can see, no other LSM can do what I want...

Quoted 6 lines
Quoted 3 lines

Sure. I am curious, though, is there a way for SELinux (or maybe Smack, since it has more dynamic labels) to declare this kind of on-runtime PTRACE relationship? Maybe I overlooked some options for this. I didn't see any

In SELinux, you could give a debugger or crash handler an unprivileged, but allowed-to-ptrace-the-main-app domain.

Right, same for AppArmor. With either system I can declare a binary as able to PTRACE another binary. This is _still_ too permissive, IMO. I want a process to directly specify which other process should be allowed to do a PTRACE. The logic for this is, by its nature, only known to the tracee. (i.e. "Oh, I'm crashing now... trigger handler... allow PTRACE.")

(Though obviously this isn't safe if the crasher handler allows arbitrary control of the process -- otherwise "kill -SEGV ..." is all that's needed to subvert the tracee. The handler by its nature should just collect details and quit. It's not a "debugging" case, it's a "crash" case.)

Quoted 9 lines
Quoted 6 lines

I still think simple chaining is the way to go. I want to review the earlier discussions first (I think Serge said it was in 2004ish?) before I write up anything. There is, I think, one sticking point, which is /proc/self/attr/current, but beyond that, I think some simple reorganization of LSM initialization routines and a list that security_* walks would be sufficient.

In the past, output results for each LSM were simply split by \n or a :
or something, and input was prepended by the LSM name.

This doesn't appear to be true anymore. Looking at the fs/proc/base.c and security/selinux/hooks.c code, there is no checking for a prepended LSM name. Maybe that's the first chaining limitation -- you can't chain 2 LSMs that both declare setprocattr hooks.

-Kees

Signature · 2 lines
Kees Cook
Ubuntu Security Team
maintainer
#18 lore raw
Quoted 33 lines · Kees Cook
Quoted 4 lines · Serge E. Hallyn

First off, if you consider PTRACE_PTRACEME, and just consider this a more finegrained targeted version of that, it doesn't seem all that gross. So maybe that's my fault for suggesting prctl as an easier-to-use in LSMs api, bc using a PTRACE_PTRACEDBY might just look cleaner.

Right, this was my thinking -- there is already one kind of declared relationship via TRACEME (though it's utility is for "pure" debugging). The other "regular" use of PTRACE is crash handlers, for which this is no declared relationship. (If you ignore simple DAC, of course.) The third PTRACE use is "arbitrary" debugging -- sysadmins or the like saying "wtf is that process DOING?"

When thinking about the PTRACE stuff originally, I hadn't realized the "crash handler" case. So "pure" was done via TRACEME, and "arbitrary" was done via CAP_SYS_PTRACE, but there wasn't a clear way to declare the "crash" case.

Quoted 10 lines

Still, you say 'ptrace is too permissive', but a rebuttal to that is that, in a DAC system, ptrace uses what credentials it knows of to authorize. *You* can make it more finegrained by not insisting on running everything as a single user.

What you now are trying to do is find a new, natural relationship between tasks on a plain DAC system to provide finer-grained control. The one you picked - process ancestry - doesn't perfectly fit, so you add changes and make it less clean. The criticism of that is valid and needs to be discusssed.

Actually, if you throw out process ancestry completely, and just use TRACEME and TRACEBY, everything still works. The idea would be to just toss out the old definition of DAC PTRACE permissions.

Quoted 5 lines
Quoted 2 lines

One q then is whether YAMA wants to provide task tracking of its own, or stack with apparmor.

This is why I asked the question below... I don't want to reinvent the wheel, but from what I can see, no other LSM can do what I want...

(see below)

Quoted 9 lines
Quoted 6 lines
Quoted 3 lines

Sure. I am curious, though, is there a way for SELinux (or maybe Smack, since it has more dynamic labels) to declare this kind of on-runtime PTRACE relationship? Maybe I overlooked some options for this. I didn't see any

In SELinux, you could give a debugger or crash handler an unprivileged, but allowed-to-ptrace-the-main-app domain.

Right, same for AppArmor. With either system I can declare a binary as able to PTRACE another binary. This is _still_ too permissive, IMO. I

In SELinux you can specify which security contexts can be targets too. I.e.

	allow serge_t serge_firefox_t:process ptrace;

I guess that in apparmor, it probably wouldn't quite be conventional to specify '/usr/bin/firefox' as a target meaning any task confined in the /usr/bin/firefox profile...

In smack, tasks have simple labels just like objects, and I believe ptrace is taken as rw access to the label.

So, you say that

> wheel, but from what I can see, no other LSM can do what I want...

It depends on if you want exactly what you say you want. You can do fine-grained ptrace controls based on security domains of both source and target. As for specifying one specific pid of a task which may ptrace me, no, that doesn't work right now, but I'm not convinced it'd be a good thing.

Quoted 16 lines

want a process to directly specify which other process should be allowed to do a PTRACE. The logic for this is, by its nature, only known to the tracee. (i.e. "Oh, I'm crashing now... trigger handler... allow PTRACE.")

(Though obviously this isn't safe if the crasher handler allows arbitrary control of the process -- otherwise "kill -SEGV ..." is all that's needed to subvert the tracee. The handler by its nature should just collect details and quit. It's not a "debugging" case, it's a "crash" case.)

Quoted 7 lines
Quoted 6 lines

I still think simple chaining is the way to go. I want to review the earlier discussions first (I think Serge said it was in 2004ish?) before I write up anything. There is, I think, one sticking point, which is /proc/self/attr/current, but beyond that, I think some simple reorganization of LSM initialization routines and a list that security_* walks would be sufficient.

Quoted 7 lines
Quoted 2 lines

In the past, output results for each LSM were simply split by \n or a :
or something, and input was prepended by the LSM name.

This doesn't appear to be true anymore. Looking at the fs/proc/base.c and security/selinux/hooks.c code, there is no checking for a prepended LSM name. Maybe that's the first chaining limitation -- you can't chain 2 LSMs that both declare setprocattr hooks.

No no, Stephen and I were talking about in the stacker patchset, again around 2004-2005. Never went upstream (per 2005 or 2006 ksummit agreement).

-serge

#19 lore raw
Quoted 11 lines · Serge E. Hallyn
Quoted 10 lines · Kees Cook
Quoted 9 lines
Quoted 6 lines

I still think simple chaining is the way to go. I want to review the earlier discussions first (I think Serge said it was in 2004ish?) before I write up anything. There is, I think, one sticking point, which is /proc/self/attr/current, but beyond that, I think some simple reorganization of LSM initialization routines and a list that security_* walks would be sufficient.

In the past, output results for each LSM were simply split by \n or a :
or something, and input was prepended by the LSM name.

Quoted 8 lines
Quoted 4 lines

This doesn't appear to be true anymore. Looking at the fs/proc/base.c and security/selinux/hooks.c code, there is no checking for a prepended LSM name. Maybe that's the first chaining limitation -- you can't chain 2 LSMs that both declare setprocattr hooks.

No no, Stephen and I were talking about in the stacker patchset, again around 2004-2005. Never went upstream (per 2005 or 2006 ksummit agreement).

Patch series was also available from:
http://sourceforge.net/projects/lsm-stacker/files/

Looks like it was last updated in 2006.

Signature · 2 lines
Stephen Smalley
National Security Agency