diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c --- a/sys/kern/sys_process.c +++ b/sys/kern/sys_process.c @@ -1261,7 +1261,7 @@ SIGSTOP); } td3->td_dbgflags &= ~(TDB_XSIG | TDB_FSTP | - TDB_SUSPEND); + TDB_SUSPEND | TDB_BORN); } if ((p->p_flag2 & P2_PTRACE_FSTP) != 0) { diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c --- a/tests/sys/kern/ptrace_test.c +++ b/tests/sys/kern/ptrace_test.c @@ -4318,6 +4318,138 @@ REQUIRE_EQ(close(pd), 0); } +static void * +detach_thread(void *arg) +{ + volatile int *quit = arg; + pthread_t thread; + + while (!*quit) { + for (int i = 0; i < 4; i++) { + pthread_create(&thread, NULL, simple_thread, NULL); + pthread_setname_np(thread, "simple"); + pthread_detach(thread); + } + usleep(100); + } + + return (NULL); +} + +static __dead2 void +detach_thread_main(volatile int *quit) +{ + pthread_t thread[4]; + + for (u_int i = 1; i < nitems(thread); i++) { + CHILD_REQUIRE_EQ(pthread_create(&thread[i], NULL, + detach_thread, __DEVOLATILE(void *, quit)), 0); + CHILD_REQUIRE_EQ(pthread_setname_np(thread[i], "spawner"), 0); + } + + for (u_int i = 1; i < nitems(thread); i++) + CHILD_REQUIRE_EQ(pthread_join(thread[i], NULL), 0); + + exit(0); +} + +/* + * Trigger a race between creating new threads and PT_DETACH. The + * race requires a thread having been created but not yet executed + * when PT_DETACH is invoked. The child process should gracefully + * exit without crashing. + */ +ATF_TC_WITHOUT_HEAD(ptrace__detach_thread_create); +ATF_TC_BODY(ptrace__detach_thread_create, tc) +{ + struct ptrace_lwpinfo pl; + struct ptrace_io_desc piod; + pid_t fpid, wpid; + volatile int quit; + lwpid_t *lwps; + int nlwps, rv, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + quit = 0; + trace_me(); + detach_thread_main(&quit); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_LWP_EVENTS, wpid, NULL, 1), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * Wait until there is a thread with TDB_BORN set that has not + * raised the associated SIGTRAP. + */ + nlwps = 0; + lwps = NULL; + for (;;) { + bool triggered; + + /* The event should be for a child thread's birth or exit. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_EXITED)) != 0); + + /* Check for a newborn thread. */ + ATF_REQUIRE((rv = ptrace(PT_GETNUMLWPS, wpid, NULL, 0)) >= 1); + + if (rv > nlwps) { + ATF_REQUIRE((lwps = realloc(lwps, + rv * sizeof(*lwps))) != NULL); + nlwps = rv; + } + ATF_REQUIRE((rv = ptrace(PT_GETLWPLIST, wpid, (caddr_t)lwps, + nlwps)) >= 1); + + triggered = false; + for (int i = 0; i < rv; i++) { + ATF_REQUIRE(ptrace(PT_LWPINFO, lwps[i], (caddr_t)&pl, + sizeof(pl)) != -1); + if (pl.pl_flags == PL_FLAG_BORN) { + triggered = true; + break; + } + } + if (triggered) + break; + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } + + /* Set "quit" to 1 in the child. */ + quit = 1; + piod.piod_op = PIOD_WRITE_D; + piod.piod_offs = __DEVOLATILE(void *, &quit); + piod.piod_addr = __DEVOLATILE(void *, &quit); + piod.piod_len = sizeof(quit); + REQUIRE_EQ(ptrace(PT_IO, fpid, (caddr_t)&piod, 0), 0); + ATF_REQUIRE(piod.piod_len == sizeof(quit)); + + REQUIRE_EQ(ptrace(PT_DETACH, fpid, (caddr_t)1, 0), 0); + + /* Wait for the child to exit. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); +} + ATF_TP_ADD_TCS(tp) { @@ -4384,6 +4516,7 @@ ATF_TP_ADD_TC(tp, ptrace__proc_reparent); ATF_TP_ADD_TC(tp, ptrace__procdesc_wait_child); ATF_TP_ADD_TC(tp, ptrace__procdesc_reparent_wait_child); + ATF_TP_ADD_TC(tp, ptrace__detach_thread_create); return (atf_no_error()); }