Skip to contentSkip to conversation

fork.c: copy_process(): fix cleanup WRT perf_event_free_task()

PATCH In review Sylvain 'ythier' Hitier 6 participants 8 messages Sep 26, 2014 – Oct 3, 2014

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 fork.c: copy_process(): fix cleanup WRT perf_event_free_task()

Date: Fri Sep 26 20:56:07 2014 +0000

fork.c: copy_process(): fix cleanup WRT perf_event_free_task()

Currently, in copy_process(), a failure of either sched_fork() or perf_event_init_task() does trigger perf_event_free_task()!

Moreover, the label bad_fork_cleanup_policy does more than what its name implies, because it includes perf_event_free_task()!

Let's explain the change with a grASCIIcally-enhanced kind-of-diff which provides an adequate context.

                                        // Read vertically this column
                                        //   |  |  |  |  |  |  |  |  |
                                        //   v  v  v  v  v  v  v  v  v

{
//SNIP//
if (clone_flags & CLONE_THREAD)
threadgroup_change_begin(current);
//SNIP//

 #ifdef CONFIG_NUMA
    p->mempolicy = mpol_dup(p->mempolicy);
    if (IS_ERR(p->mempolicy)) {
//SNIP//
        goto bad_fork_cleanup_threadgroup_lock;
    }
 #endif
//SNIP//
    retval = sched_fork(clone_flags, p);
    if (retval)
//                                      // mustn't perf_event_free_task()
        goto bad_fork_cleanup_policy;
    retval = perf_event_init_task(p);
    if (retval)
//                                      // mustn't perf_event_free_task()
        goto bad_fork_cleanup_policy;
    retval = audit_alloc(p);
    if (retval)

// // must perf_event_free_task()
// @@ Hence change this way:
- goto bad_fork_cleanup_policy;
+ goto bad_fork_cleanup_perf;
//SNIP//
bad_fork_cleanup_audit:

    audit_free(p);

// // let's clean perf up
// @@ Hence change this way:
-bad_fork_cleanup_policy:
+bad_fork_cleanup_perf:

    perf_event_free_task(p);
//                                      // no (longer) need to clean perf up
//                                      @@ Hence change this way:
+bad_fork_cleanup_policy:
 #ifdef CONFIG_NUMA
    mpol_put(p->mempolicy);

bad_fork_cleanup_threadgroup_lock:
#endif
if (clone_flags & CLONE_THREAD)
threadgroup_change_end(current);
//SNIP//
}

---
 kernel/fork.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/fork.c +3 -2
index 0cf9cdb..a91e47d 100644
@@ -1360,7 +1360,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 		goto bad_fork_cleanup_policy;
 	retval = audit_alloc(p);
 	if (retval)
-		goto bad_fork_cleanup_policy;
+		goto bad_fork_cleanup_perf;
 	/* copy all the process information */
 	shm_init_task(p);
 	retval = copy_semundo(clone_flags, p);
@@ -1566,8 +1566,9 @@ bad_fork_cleanup_semundo:
 	exit_sem(p);
 bad_fork_cleanup_audit:
 	audit_free(p);
-bad_fork_cleanup_policy:
+bad_fork_cleanup_perf:
 	perf_event_free_task(p);
+bad_fork_cleanup_policy:
 #ifdef CONFIG_NUMA
 	mpol_put(p->mempolicy);
 bad_fork_cleanup_threadgroup_lock:

Regards,
Sylvain "ythier" Hitier

Signature · 3 lines
Business is about being busy, not being rich...
Lived 777 days in a Debian package => http://en.wikipedia.org/wiki/Apt,_Vaucluse
There's THE room for ideals in this mechanical place!
#1 lore raw
Quoted 5 lines · Sylvain 'ythier' Hitier
    retval = sched_fork(clone_flags, p);
    if (retval)
//                                      // mustn't perf_event_free_task()
        goto bad_fork_cleanup_policy;

Agreed, this is wrong. Good catch.

but, unless I missed something,

Quoted 3 lines
    retval = perf_event_init_task(p);
    if (retval)

// // mustn't perf_event_free_task()

                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

this is not right and thus the patch is not right too.

Suppose that perf_event_init_task() -> perf_event_init_context(ctxn => 0) succeeds and then perf_event_init_context(ctxn => 1) fails, we need perf_event_free_task() to cleanup ->perf_event_ctxp[0].

So if perf_event_init_task() fails, we still need "goto bad_fork_cleanup_perf".

No?

Or, probably better, we need to change perf_event_init_context() to call perf_event_free_task() on failure.

Or. We can simply move memset(child->perf_event_ctxp, 0, ...) from perf_event_init_context() up. This reminds that we really need to cleanup copy_process(), in particular I think it asks for the new copy_xxx() helper which should do misc simple initializations which can't fail.

What do you think?

Oleg.

maintainer
#2 lore raw
Quoted 10 lines · Oleg Nesterov
Quoted 5 lines · Sylvain 'ythier' Hitier
    retval = sched_fork(clone_flags, p);
    if (retval)
//                                      // mustn't perf_event_free_task()
        goto bad_fork_cleanup_policy;

Agreed, this is wrong. Good catch.

but, unless I missed something,

Ah, indeed. It was meant to be a no-op there, but its before we do that memset, so its still the inherited values, and we don't want to clean those up I think.

Quoted 6 lines
Quoted 3 lines
    retval = perf_event_init_task(p);
    if (retval)

// // mustn't perf_event_free_task()

                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

this is not right and thus the patch is not right too.

Agreed

Quoted 7 lines

Suppose that perf_event_init_task() -> perf_event_init_context(ctxn => 0) succeeds and then perf_event_init_context(ctxn => 1) fails, we need perf_event_free_task() to cleanup ->perf_event_ctxp[0].

So if perf_event_init_task() fails, we still need "goto bad_fork_cleanup_perf".

No?

Yep

Quoted 9 lines

Or, probably better, we need to change perf_event_init_context() to call perf_event_free_task() on failure.

Or. We can simply move memset(child->perf_event_ctxp, 0, ...) from perf_event_init_context() up. This reminds that we really need to cleanup copy_process(), in particular I think it asks for the new copy_xxx() helper which should do misc simple initializations which can't fail.

What do you think?

I prefer the former, as the latter scatters the perf specific bits over more places. Something like so then?

---
Subject: perf: Fix perf bug in fork()

Oleg noticed that a cleanup by Sylvain actually uncovered a bug; by calling perf_event_free_task() when failing sched_fork() we will not yet have done the memset() on ->perf_event_ctxp[] and will therefore try and 'free' the inherited contexts, which are still in use by the parent process. This is bad..

---

kernel/events/core.c +3 -1
index a232b40..4a0dbb2 100644
@@ -8078,8 +8078,10 @@ int perf_event_init_task(struct task_struct *child)
 
 	for_each_task_context_nr(ctxn) {
 		ret = perf_event_init_context(child, ctxn);
-		if (ret)
+		if (ret) {
+			perf_event_free_task(child);
 			return ret;
+		}
 	}
 
 	return 0;
kernel/fork.c +3 -2
index ad64248..b6cc3f2 100644
@@ -1367,7 +1367,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 		goto bad_fork_cleanup_policy;
 	retval = audit_alloc(p);
 	if (retval)
-		goto bad_fork_cleanup_policy;
+		goto bad_fork_cleanup_perf;
 	/* copy all the process information */
 	shm_init_task(p);
 	retval = copy_semundo(clone_flags, p);
@@ -1573,8 +1573,9 @@ bad_fork_cleanup_semundo:
 	exit_sem(p);
 bad_fork_cleanup_audit:
 	audit_free(p);
-bad_fork_cleanup_policy:
+bad_fork_cleanup_perf:
 	perf_event_free_task(p);
+bad_fork_cleanup_policy:
 #ifdef CONFIG_NUMA
 	mpol_put(p->mempolicy);
 bad_fork_cleanup_threadgroup_lock:
#3 lore raw
Quoted 8 lines · Peter Zijlstra

Subject: perf: Fix perf bug in fork()

Oleg noticed that a cleanup by Sylvain actually uncovered a bug; by calling perf_event_free_task() when failing sched_fork() we will not yet have done the memset() on ->perf_event_ctxp[] and will therefore try and 'free' the inherited contexts, which are still in use by the parent process. This is bad..

Quoted 4 lines

Could this fix a couple of fuzzer triggered perf crashes perhaps?

Thanks,

Ingo

maintainer
#4 lore raw
Quoted 11 lines · Ingo Molnar
Quoted 8 lines · Peter Zijlstra

Subject: perf: Fix perf bug in fork()

Oleg noticed that a cleanup by Sylvain actually uncovered a bug; by calling perf_event_free_task() when failing sched_fork() we will not yet have done the memset() on ->perf_event_ctxp[] and will therefore try and 'free' the inherited contexts, which are still in use by the parent process. This is bad..

Quoted 6 lines
Quoted 4 lines

Could this fix a couple of fuzzer triggered perf crashes perhaps?

It could indeed I suppose.. you never know what paths those fuzzers manage to hit.

maintainer
#5 lore raw
Quoted 17 lines · On Mon, 29 Sep 2014 16:00:48 +0200 Peter Zijlstra <[email protected]> wrote:
Quoted 16 lines · Ingo Molnar
Quoted 12 lines · Peter Zijlstra

Subject: perf: Fix perf bug in fork()

Oleg noticed that a cleanup by Sylvain actually uncovered a bug; by calling perf_event_free_task() when failing sched_fork() we will not yet have done the memset() on ->perf_event_ctxp[] and will therefore try and 'free' the inherited contexts, which are still in use by the parent process. This is bad..

Quoted 4 lines
Quoted 1 line

Could this fix a couple of fuzzer triggered perf crashes perhaps?

It could indeed I suppose.. you never know what paths those fuzzers manage to hit.

The patch isn't in linux-next and didn't cc stable. I think I'll squirt it Linuswards later this week unless someone stops me..

#7 lore raw

[tip:perf/urgent] perf: Fix perf bug in fork()

In reply to Peter Zijlstra
perf: Fix perf bug in fork()
Commit-ID:  9c2b9d30e28559a78c9e431cdd7f2c6bf5a9ee67
Gitweb:     http://git.kernel.org/tip/9c2b9d30e28559a78c9e431cdd7f2c6bf5a9ee67
Author:     Peter Zijlstra <[email protected]>
AuthorDate: Mon, 29 Sep 2014 12:12:01 +0200
Committer:  Ingo Molnar <[email protected]>
CommitDate: Fri, 3 Oct 2014 05:41:08 +0200

perf: Fix perf bug in fork()

Oleg noticed that a cleanup by Sylvain actually uncovered a bug; by calling perf_event_free_task() when failing sched_fork() we will not yet have done the memset() on ->perf_event_ctxp[] and will therefore try and 'free' the inherited contexts, which are still in use by the parent process.

This is bad and might explain some outstanding fuzzer failures ...

---
 kernel/events/core.c | 4 +++-
 kernel/fork.c        | 5 +++--
 2 files changed, 6 insertions(+), 3 deletions(-)
kernel/events/core.c +3 -1
index afdd9e1..658f232 100644
@@ -7956,8 +7956,10 @@ int perf_event_init_task(struct task_struct *child)
 
 	for_each_task_context_nr(ctxn) {
 		ret = perf_event_init_context(child, ctxn);
-		if (ret)
+		if (ret) {
+			perf_event_free_task(child);
 			return ret;
+		}
 	}
 
 	return 0;
kernel/fork.c +3 -2
index 0cf9cdb..a91e47d 100644
@@ -1360,7 +1360,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 		goto bad_fork_cleanup_policy;
 	retval = audit_alloc(p);
 	if (retval)
-		goto bad_fork_cleanup_policy;
+		goto bad_fork_cleanup_perf;
 	/* copy all the process information */
 	shm_init_task(p);
 	retval = copy_semundo(clone_flags, p);
@@ -1566,8 +1566,9 @@ bad_fork_cleanup_semundo:
 	exit_sem(p);
 bad_fork_cleanup_audit:
 	audit_free(p);
-bad_fork_cleanup_policy:
+bad_fork_cleanup_perf:
 	perf_event_free_task(p);
+bad_fork_cleanup_policy:
 #ifdef CONFIG_NUMA
 	mpol_put(p->mempolicy);
 bad_fork_cleanup_threadgroup_lock: