Skip to contentSkip to conversation

+ execve-must-clear-current-clear_child_tid.patch added to -mm tree

PATCH In review [email protected] 4 participants 8 messages Jul 31, 2009 – Aug 1, 2009

Subsystems (3)
lore
Share & tools
lore

[email protected]

author maintainer tree notice
#0 lore raw

The patch titled

     execve: must clear current->clear_child_tid

has been added to the -mm tree. Its filename is
execve-must-clear-current-clear_child_tid.patch

Before you just go and hit "reply", please:
a) Consider who else should be cc'ed
b) Prefer to cc a suitable mailing list as well
c) Ideally: find the original patch on the mailing list and do a reply-to-all to that, adding suitable additional cc's

*** Remember to use Documentation/SubmitChecklist when testing your code ***

See http://userweb.kernel.org/~akpm/stuff/added-to-mm.txt to find out what to do about this

The current -mm tree may be found at http://userweb.kernel.org/~akpm/mmotm/

------------------------------------------------------
Subject: execve: must clear current->clear_child_tid
From: Eric Dumazet <[email protected]>

While looking at Jens Rosenboom bug report
(http://lkml.org/lkml/2009/7/27/35) about strange sys_futex call done from a dying "ps" program, we found following problem.

clone() syscall has special support for TID of created threads. This support includes two features.

One (CLONE_CHILD_SETTID) is to set an integer into user memory with the TID value.

One (CLONE_CHILD_CLEARTID) is to clear this same integer once the created thread dies.

The integer location is a user provided pointer, provided at clone() time.

kernel keeps this pointer value into current->clear_child_tid.

At execve() time, we should make sure kernel doesnt keep this user provided pointer, as full user memory is replaced by a new one.

As glibc fork() actually uses clone() syscall with CLONE_CHILD_SETTID and CLONE_CHILD_CLEARTID set, chances are high that we might corrupt user memory in forked processes.

Following sequence could happen:

1) bash (or any program) starts a new process, by a fork() call that glibc maps to a clone( ... CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID ...) syscall

2) When new process starts, its current->clear_child_tid is set to a location that has a meaning only in bash (or initial program) context (&THREAD_SELF->tid)

3) This new process does the execve() syscall to start a new program. current->clear_child_tid is left unchanged (a non NULL value)

4) If this new program creates some threads, and initial thread exits, kernel will attempt to clear the integer pointed by current->clear_child_tid from mm_release() :

        if (tsk->clear_child_tid
            && !(tsk->flags & PF_SIGNALED)
            && atomic_read(&mm->mm_users) > 1) {
                u32 __user * tidptr = tsk->clear_child_tid;
                tsk->clear_child_tid = NULL;

                /*
                 * We don't check the error code - if userspace has
                 * not set up a proper pointer then tough luck.
                 */
<< here >>      put_user(0, tidptr);
                sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
        }

5) OR : if new program is not multi-threaded, but spied by /proc/pid users (ps command for example), mm_users > 1, and the exiting program could corrupt 4 bytes in a persistent memory area (shm or memory mapped file)

If current->clear_child_tid points to a writeable portion of memory of the new program, kernel happily and silently corrupts 4 bytes of memory, with unexpected effects.

Fix is straightforward and should not break any sane program.

---

 fs/compat.c |    1 +
 fs/exec.c   |    1 +
 2 files changed, 2 insertions(+)
fs/compat.c renamed from fs/compat.c~execve-must-clear-current-clear_child_tid +1 -0
diff -puN fs/compat.c~execve-must-clear-current-clear_child_tid fs/compat.c
@@ -1550,6 +1550,7 @@ int compat_do_execve(char * filename,
 	mutex_unlock(&current->cred_guard_mutex);
 	acct_update_integrals(current);
 	free_bprm(bprm);
+	current->clear_child_tid = NULL;
 	if (displaced)
 		put_files_struct(displaced);
 	return retval;
fs/exec.c renamed from fs/exec.c~execve-must-clear-current-clear_child_tid +1 -0
diff -puN fs/exec.c~execve-must-clear-current-clear_child_tid fs/exec.c
@@ -1343,6 +1343,7 @@ int do_execve(char * filename,
 	mutex_unlock(&current->cred_guard_mutex);
 	acct_update_integrals(current);
 	free_bprm(bprm);
+	current->clear_child_tid = NULL;
 	if (displaced)
 		put_files_struct(displaced);
 	return retval;

_

Patches currently in -mm which might be from [email protected] are

linux-next.patch
execve-must-clear-current-clear_child_tid.patch

#1 lore raw
Quoted 17 lines

From: Eric Dumazet <[email protected]>

diff -puN fs/compat.c~execve-must-clear-current-clear_child_tid fs/compat.c

fs/compat.c renamed from fs/compat.c~execve-must-clear-current-clear_child_tid +1 -0
@@ -1550,6 +1550,7 @@ int compat_do_execve(char * filename,
 	mutex_unlock(&current->cred_guard_mutex);
 	acct_update_integrals(current);
 	free_bprm(bprm);
+	current->clear_child_tid = NULL;
 	if (displaced)
 		put_files_struct(displaced);
 	return retval;

diff -puN fs/exec.c~execve-must-clear-current-clear_child_tid fs/exec.c

fs/exec.c int do_execve(char * filename, · line 1349

Quoted 7 lines
fs/exec.c renamed from fs/exec.c~execve-must-clear-current-clear_child_tid +1 -0
@@ -1343,6 +1343,7 @@ int do_execve(char * filename,
 	mutex_unlock(&current->cred_guard_mutex);
 	acct_update_integrals(current);
 	free_bprm(bprm);
+	current->clear_child_tid = NULL;
 	if (displaced)
 		put_files_struct(displaced);
 	return retval;

Perhaps it is better to change mm_release() ? It has to play with ->clear_child_tid anyway.

Oleg.

kernel/fork.c +11 -11
@@ -568,18 +568,18 @@ void mm_release(struct task_struct *tsk,
 	 * the value intact in a core dump, and to save the unnecessary
 	 * trouble otherwise.  Userland only wants this done for a sys_exit.
 	 */
-	if (tsk->clear_child_tid
-	    && !(tsk->flags & PF_SIGNALED)
-	    && atomic_read(&mm->mm_users) > 1) {
-		u32 __user * tidptr = tsk->clear_child_tid;
+	if (tsk->clear_child_tid) {
+		if (!(tsk->flags & PF_SIGNALED) &&
+		    atomic_read(&mm->mm_users) > 1) {
+			/*
+			 * We don't check the error code - if userspace has
+			 * not set up a proper pointer then tough luck.
+			 */
+			put_user(0, tsk->clear_child_tid);
+			sys_futex(tsk->clear_child_tid, FUTEX_WAKE,
+					1, NULL, NULL, 0);
+		}
 		tsk->clear_child_tid = NULL;
-
-		/*
-		 * We don't check the error code - if userspace has
-		 * not set up a proper pointer then tough luck.
-		 */
-		put_user(0, tidptr);
-		sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
 	}
 }
 
#2 lore raw
Quoted 3 lines · Oleg Nesterov

Perhaps it is better to change mm_release() ? It has to play with ->clear_child_tid anyway.

Ahh. I take back my previous Ack. Your patch is better. I'll ack that instead.

Linus

author maintainer
#3 lore raw
Quoted 10 lines · On Fri, 31 Jul 2009 17:38:14 -0700 (PDT) Linus Torvalds…
Quoted 3 lines · Oleg Nesterov

Perhaps it is better to change mm_release() ? It has to play with ->clear_child_tid anyway.

Ahh. I take back my previous Ack. Your patch is better. I'll ack that instead.

'k, thanks. I shall compulsively watch my inbox awaiting the signed-off and tested version ;)

#4 lore raw

PATCH v2 1/1 execve: must clear current->clear_child_tid

Quoted 10 lines · Andrew Morton
Quoted 7 lines · On Fri, 31 Jul 2009 17:38:14 -0700 (PDT) Linus Torvalds…
Quoted 3 lines · Oleg Nesterov

Perhaps it is better to change mm_release() ? It has to play with ->clear_child_tid anyway.

Quoted 6 lines
Quoted 3 lines

Ahh. I take back my previous Ack. Your patch is better. I'll ack that instead.

'k, thanks. I shall compulsively watch my inbox awaiting the signed-off and tested version ;)

I did some testing, but didn't try to check if this patches fixes the origianal problem. It obviously should... Still I removed Tested-by tag. But added Linus's ack, the patch is the same.

------------------------------------------------------------------------------
[PATCH v2] execve: must clear current->clear_child_tid

From: Eric Dumazet <[email protected]>

While looking at Jens Rosenboom bug report
(http://lkml.org/lkml/2009/7/27/35) about strange sys_futex call done from a dying "ps" program, we found following problem.

clone() syscall has special support for TID of created threads. This support includes two features.

One (CLONE_CHILD_SETTID) is to set an integer into user memory with the TID value.

One (CLONE_CHILD_CLEARTID) is to clear this same integer once the created thread dies.

The integer location is a user provided pointer, provided at clone() time.

kernel keeps this pointer value into current->clear_child_tid.

At execve() time, we should make sure kernel doesnt keep this user provided pointer, as full user memory is replaced by a new one.

As glibc fork() actually uses clone() syscall with CLONE_CHILD_SETTID and CLONE_CHILD_CLEARTID set, chances are high that we might corrupt user memory in forked processes.

Following sequence could happen:

1) bash (or any program) starts a new process, by a fork() call that glibc maps to a clone( ... CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID ...) syscall

2) When new process starts, its current->clear_child_tid is set to a location that has a meaning only in bash (or initial program) context (&THREAD_SELF->tid)

3) This new process does the execve() syscall to start a new program. current->clear_child_tid is left unchanged (a non NULL value)

4) If this new program creates some threads, and initial thread exits, kernel will attempt to clear the integer pointed by current->clear_child_tid from mm_release() :

        if (tsk->clear_child_tid
            && !(tsk->flags & PF_SIGNALED)
            && atomic_read(&mm->mm_users) > 1) {
                u32 __user * tidptr = tsk->clear_child_tid;
                tsk->clear_child_tid = NULL;

                /*
                 * We don't check the error code - if userspace has
                 * not set up a proper pointer then tough luck.
                 */
<< here >>      put_user(0, tidptr);
                sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
        }

5) OR : if new program is not multi-threaded, but spied by /proc/pid users (ps command for example), mm_users > 1, and the exiting program could corrupt 4 bytes in a persistent memory area (shm or memory mapped file)

If current->clear_child_tid points to a writeable portion of memory of the new program, kernel happily and silently corrupts 4 bytes of memory, with unexpected effects.

Fix is straightforward and should not break any sane program.

---

 kernel/fork.c |   22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)
kernel/fork.c renamed from kernel/fork.c~CLEARTID +11 -11
@@ -568,18 +568,18 @@ void mm_release(struct task_struct *tsk,
 	 * the value intact in a core dump, and to save the unnecessary
 	 * trouble otherwise.  Userland only wants this done for a sys_exit.
 	 */
-	if (tsk->clear_child_tid
-	    && !(tsk->flags & PF_SIGNALED)
-	    && atomic_read(&mm->mm_users) > 1) {
-		u32 __user * tidptr = tsk->clear_child_tid;
+	if (tsk->clear_child_tid) {
+		if (!(tsk->flags & PF_SIGNALED) &&
+		    atomic_read(&mm->mm_users) > 1) {
+			/*
+			 * We don't check the error code - if userspace has
+			 * not set up a proper pointer then tough luck.
+			 */
+			put_user(0, tsk->clear_child_tid);
+			sys_futex(tsk->clear_child_tid, FUTEX_WAKE,
+					1, NULL, NULL, 0);
+		}
 		tsk->clear_child_tid = NULL;
-
-		/*
-		 * We don't check the error code - if userspace has
-		 * not set up a proper pointer then tough luck.
-		 */
-		put_user(0, tidptr);
-		sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
 	}
 }
 
#5 lore raw

Re: [PATCH v2] execve: must clear current->clear_child_tid

Quoted 121 lines · Oleg Nesterov
Quoted 11 lines · Andrew Morton
Quoted 7 lines · On Fri, 31 Jul 2009 17:38:14 -0700 (PDT) Linus Torvalds…
Quoted 2 lines · Oleg Nesterov

Perhaps it is better to change mm_release() ? It has to play with ->clear_child_tid anyway.

Ahh. I take back my previous Ack. Your patch is better. I'll ack that instead.

'k, thanks. I shall compulsively watch my inbox awaiting the signed-off and tested version ;)

I did some testing, but didn't try to check if this patches fixes the origianal problem. It obviously should... Still I removed Tested-by tag. But added Linus's ack, the patch is the same.

------------------------------------------------------------------------------
[PATCH v2] execve: must clear current->clear_child_tid

From: Eric Dumazet <[email protected]>

While looking at Jens Rosenboom bug report
(http://lkml.org/lkml/2009/7/27/35) about strange sys_futex call done from a dying "ps" program, we found following problem.

clone() syscall has special support for TID of created threads. This support includes two features.

One (CLONE_CHILD_SETTID) is to set an integer into user memory with the TID value.

One (CLONE_CHILD_CLEARTID) is to clear this same integer once the created thread dies.

The integer location is a user provided pointer, provided at clone() time.

kernel keeps this pointer value into current->clear_child_tid.

At execve() time, we should make sure kernel doesnt keep this user provided pointer, as full user memory is replaced by a new one.

As glibc fork() actually uses clone() syscall with CLONE_CHILD_SETTID and CLONE_CHILD_CLEARTID set, chances are high that we might corrupt user memory in forked processes.

Following sequence could happen:

1) bash (or any program) starts a new process, by a fork() call that glibc maps to a clone( ... CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID ...) syscall

2) When new process starts, its current->clear_child_tid is set to a location that has a meaning only in bash (or initial program) context (&THREAD_SELF->tid)

3) This new process does the execve() syscall to start a new program. current->clear_child_tid is left unchanged (a non NULL value)

4) If this new program creates some threads, and initial thread exits, kernel will attempt to clear the integer pointed by current->clear_child_tid from mm_release() :

        if (tsk->clear_child_tid
            && !(tsk->flags & PF_SIGNALED)
            && atomic_read(&mm->mm_users) > 1) {
                u32 __user * tidptr = tsk->clear_child_tid;
                tsk->clear_child_tid = NULL;

                /*
                 * We don't check the error code - if userspace has
                 * not set up a proper pointer then tough luck.
                 */
<< here >>      put_user(0, tidptr);
                sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
        }

5) OR : if new program is not multi-threaded, but spied by /proc/pid users (ps command for example), mm_users > 1, and the exiting program could corrupt 4 bytes in a persistent memory area (shm or memory mapped file)

If current->clear_child_tid points to a writeable portion of memory of the new program, kernel happily and silently corrupts 4 bytes of memory, with unexpected effects.

Fix is straightforward and should not break any sane program.

---

 kernel/fork.c |   22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)
kernel/fork.c renamed from kernel/fork.c~CLEARTID +11 -4
@@ -568,18 +568,18 @@ void mm_release(struct task_struct *tsk,
 	 * the value intact in a core dump, and to save the unnecessary
 	 * trouble otherwise.  Userland only wants this done for a sys_exit.
 	 */
-	if (tsk->clear_child_tid
-	    && !(tsk->flags & PF_SIGNALED)
-	    && atomic_read(&mm->mm_users) > 1) {
-		u32 __user * tidptr = tsk->clear_child_tid;
+	if (tsk->clear_child_tid) {
+		if (!(tsk->flags & PF_SIGNALED) &&
+		    atomic_read(&mm->mm_users) > 1) {
+			/*
+			 * We don't check the error code - if userspace has
+			 * not set up a proper pointer then tough luck.
+			 */
+			put_user(0, tsk->clear_child_tid);
+			sys_futex(tsk->clear_child_tid, FUTEX_WAKE,
+					1, NULL, NULL, 0);
+		}
 		tsk->clear_child_tid = NULL;

kernel/fork.c void mm_release(struct task_struct *tsk, · line 585

Quoted 10 lines
kernel/fork.c renamed from kernel/fork.c~CLEARTID +0 -7
-
-		/*
-		 * We don't check the error code - if userspace has
-		 * not set up a proper pointer then tough luck.
-		 */
-		put_user(0, tidptr);
-		sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
 	}
 }
 

Thanks Oleg, you are right this seems cleaner.

I only wonder about core dumping, since mm_release() is also used by exiting tasks.

Isnt clear_child_tid used by gdb or other debugger ?
(The tid value is carefuly untouched in case of a core dump, but maybe gdb needs the current->clear_child_tid for whatever reason, for example to get TID address in user memory ?

#6 lore raw

Re: [PATCH v2] execve: must clear current->clear_child_tid

Quoted 4 lines · Eric Dumazet

I only wonder about core dumping, since mm_release() is also used by exiting tasks.

Isnt clear_child_tid used by gdb or other debugger ?

Afaics it is not...

At least, I can't see how gdb (or any other user-space app) can figure out the value of ->clear_child_tid.

Not that this really matters, but please note also that it is possible that the coredumping task has ->clear_child_tid == NULL anyway, even without this change. The PF_SIGNALED check in mm_release() is not 100& reliable.

Suppose a thread T sleeps in do_exit()->ptrace_event(PT_TRACE_EXIT) path. Another thread starts a coredump and kills T via zap_process(). This wakes up T, it calls exit_mm()->mm_release() without PF_SIGNALED.

Oleg.

#7 lore raw

Re: [PATCH v2] execve: must clear current->clear_child_tid

Quoted 10 lines · Oleg Nesterov
Quoted 3 lines · Eric Dumazet

I only wonder about core dumping, since mm_release() is also used by exiting tasks.

Isnt clear_child_tid used by gdb or other debugger ?

Afaics it is not...

At least, I can't see how gdb (or any other user-space app) can figure out the value of ->clear_child_tid.

Yep, this was from old Unixes time, when core file included the 'u' structure ...

Dont worry ;)

Quoted 13 lines

Not that this really matters, but please note also that it is possible that the coredumping task has ->clear_child_tid == NULL anyway, even without this change. The PF_SIGNALED check in mm_release() is not 100& reliable.

Suppose a thread T sleeps in do_exit()->ptrace_event(PT_TRACE_EXIT) path. Another thread starts a coredump and kills T via zap_process(). This wakes up T, it calls exit_mm()->mm_release() without PF_SIGNALED.

Oleg.