Page MenuHomeFreeBSD

fork: avoid endless wait with PTRACE_FORK and RFSTOPPED
ClosedPublic

Authored by yanko.yankulov_gmail.com on Jun 16 2018, 7:13 PM.
Tags
None
Referenced Files
Unknown Object (File)
Thu, Dec 12, 8:55 PM
Unknown Object (File)
Wed, Dec 11, 6:32 AM
Unknown Object (File)
Tue, Dec 3, 5:26 PM
Unknown Object (File)
Mon, Dec 2, 8:54 PM
Unknown Object (File)
Fri, Nov 29, 9:52 AM
Unknown Object (File)
Nov 16 2024, 3:36 PM
Unknown Object (File)
Nov 4 2024, 5:39 PM
Unknown Object (File)
Oct 20 2024, 3:10 PM
Subscribers

Details

Summary

Issue was that RFSTOPPED thread can't clean TDB_STOPATFORK, so parent is stuck forever. Tirggered when trying to ptrace linux binary.

Instead of waiting for the new thread to clear TDB_STOPATFORK, tag it as traced in do_fork and let it only notify the debugger when run.

Diff Detail

Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

Removed the now outdated comment in do_fork

I was not able to imagine a case which is broken by this simplification. Also, the ptrace_test works with the patch.

I added John to review.

sys/kern/kern_fork.c
748

Style:

if (has_ptrace_fork) {
...
}

I am not sure about usefulness of __prefict_false() there. I agree that has_ptrace_fork is almost always false, but it is not clear if the hint makes any change.

755

Again style. Also compact the conditions into single if:

if ((p2->p_pptr->p_ptevents & PTRACE_FORK)  != 0 &&
    (td2->td_dbgflags & TDB_STOPATFORK) != 0) {
1079
if ((p->p_flag & P_TRACED) != 0) {
sys/kern/kern_fork.c
396

bool ?

748

Remove spaces after '(' and before ')',

sys/kern/kern_fork.c
748

I do wonder if we couldn't collapse the logic a bit so that it was all in one place instead of splitting it up. I think we can't move this logic up, but not sure if we couldn't move the earlier logic down into here?

751

Can you do 's/p2->p_pptr/p1/' here?

753

I would move the KTR_PTRACE for "attaching to forked child" here since this is now when the attach happens. (Would have to change the function name to no longer be fork_return)

yanko.yankulov_gmail.com added inline comments.
sys/kern/kern_fork.c
751

this is after,
_PRELE(p1);
PROC_UNLOCK(p1);

so I am not sure if p1 is still valid / parent. Don't know the locking semantics

If it is not, the proc_reparent call is probably wrong
proc_reparent(p2, p1->p_pptr);

In fact p1 is used just after the unlock, so it is probably valid, I just don't know why. And I am not sure that the use guarantees it is still p2's parent.

Any thoughts?

sys/kern/kern_fork.c
751

p1 is just a different name for curproc.

And td2 still cannot execute because it is unknown to the scheduler. sched_add() is done later, so it cannot exit and be reparented. Since p1 is curproc and we are executing in its context, p1 cannot exit and p2 cannot be re-childed (silly pun) from p1.

sys/kern/kern_fork.c
751

Yep, fork1 is called with the current thread or thread0 only, so yes p1 is safe.

Address 2 of the jhb comments.

Have no idea about moving other code around.

Address 2 of the jhb comments.

Have no idea about moving other code around.

I think John' idea was to move the block which sets the has_ptrace_fork variable to true, down to the code which acts on its true value. De-fact, eliminating the var, and removing one if().

I think John' idea was to move the block which sets the has_ptrace_fork variable to true, down to the code which acts on its true value. De-fact, eliminating the var, and removing one if().

OK, but as far as I can tell we will need p1's PROC_LOCK or the proctree_lock in order to check the PTRACE_FORK flag. Am i missing something obvious again :) ?

I think John' idea was to move the block which sets the has_ptrace_fork variable to true, down to the code which acts on its true value. De-fact, eliminating the var, and removing one if().

OK, but as far as I can tell we will need p1's PROC_LOCK or the proctree_lock in order to check the PTRACE_FORK flag. Am i missing something obvious again :) ?

We can read it lockless, and re-acquire the proc lock if set to re-read under the lock. This is not much different from reading under the lock, remembering the result, then drop the lock and use the result.

Also, I am unsure why do you state that p_ptevents requires proctree_lock.

In D15857#337018, @kib wrote:

I think John' idea was to move the block which sets the has_ptrace_fork variable to true, down to the code which acts on its true value. De-fact, eliminating the var, and removing one if().

OK, but as far as I can tell we will need p1's PROC_LOCK or the proctree_lock in order to check the PTRACE_FORK flag. Am i missing something obvious again :) ?

We can read it lockless, and re-acquire the proc lock if set to re-read under the lock. This is not much different from reading under the lock, remembering the result, then drop the lock and use the result.

Right. Obvious. Will do the changes shortly.

Also, I am unsure why do you state that p_ptevents requires proctree_lock.

In kern_ptrace PT_FOLLOW_FORK request acquires proctree_lock. It seems that this is specific to PTRACE_FORK & PTRACE_LWP.

Merge the two parts of set child as traced

Other than two notes I put inline, the patch looks fine to me.

sys/kern/kern_fork.c
748

Use the same style != 0 as at line 751.

751

After looking at your variant of code, I do not see why the recheck is useful. You do not lock p1 there, so the race with unlocked read of p1->p_ptevents is the same as without the re-read.

sys/kern/kern_fork.c
751

My reasoning:

From my last comment: p1->p_ptevents is protected for PTRACE_FORK & PTRACE_LWP by both p1's lock and proctree_lock in kern_ptrace. And all other (unlocked) modifications to p_ptevents preserve those flags.

We need to check that p1 is still traced else p1->p_pptr might not be a debugger. Both p1->p_flag P_TRACED and p1->p_ptevents PTRACE_FORK are proctected by proctree_lock, so PTRACE_FORK implies P_TRACED and that p->p_pptr is a tracer.

Thus the recheck.

sys/kern/kern_fork.c
751

You changed from the declared locking model to the de-facto occuring locking model then, this cannot happen in ad-hoc mode. At least, you need to add a note to the proc.h both to the locking annotation for p_ptevents and to the PTRACE_FORK symbol itself, and perhaps add a comment there too, explaining why p1 lock is not taken, but the recheck under proctree is enough.

sys/kern/kern_fork.c
751

I am a little confused. I can't figure out the difference in the locking before and after the patch, so maybe there is another thing I am missing.

The old code in fork_return:
sx_xlock(&proctree_lock);
PROC_LOCK(p)
if (p->p_pptr->p_ptevents & PTRACE_FORK) {
....

p->p_pptr lock is not taken, same as the:

new code in do_fork

+ sx_xlock(&proctree_lock);
+ PROC_LOCK(p2);
+ if ((p1->p_ptevents & PTRACE_FORK) != 0) {
+ /*

Will look into the annotation and adding a comment, but if the old code depended on the same guarantees probably a separate commit ?

sys/kern/kern_fork.c
751

I am not sure where to put the blame, on the old code, on the existing annotation, or both. But it is clear that according to the rules from proc.h, the read of PTRACE_FORK in the old code should be considered unlocked:

	u_int		p_ptevents;	/* (c) ptrace() event mask. */
 *      c - locked by proc mtx

I think it is fine to update the locking annotation first. Your commit should add an explicit comment into the new code still, IMO.

sys/kern/kern_fork.c
751

Thanks. My question was if I have involuntary changed something in the locking that I have totally missed.

I went through the code again and the whole p_ptevents is always protected by proctree_lock with two exceptions - cleared in exit1 and in ptracestop if process is killed. Both will not affect the code in do_fork, but what would be proper annotation - "(c + e) unless process is exiting" ?

sys/kern/kern_fork.c
751

I think just c+e is enough there.

It looks fine to me, I will wait some time for John opinion.

sys/kern/kern_fork.c
752

End the sentence with the dot.

758

Please expand it, '... are protected by both process and proctree locks for modifications, so owning proctree_lock allows the race-free read'. And the dot at the end of sentence.

761

Remove the blank line.

1081

Dot.

This revision is now accepted and ready to land.Jun 21 2018, 5:24 PM
jhb added inline comments.
sys/kern/kern_fork.c
750

I would put the speculative check comment above the speculative check instead of after it.

752

Also, s/beeing/being/

763

Probably don't need this blank line.

773

Or this blank line either.

781

Perhaps swap order of unlocks so it is symmetric? We tend to do symmetric unlocks.

This revision now requires review to proceed.Jun 21 2018, 6:51 PM
This revision is now accepted and ready to land.Jun 21 2018, 7:00 PM
This revision was automatically updated to reflect the committed changes.