Skip to content

Commit 1785ca3

Browse files
committed
test(process): reject lost fatal exec signals
Keep the post-exec image blocked so a successful exec cannot turn a lost thread-targeted SIGKILL into a normal exit. Track retirement of the exchanged TID until wait completion and only release the image after ESRCH proves that the old identity disappeared. Require every observable fatal delivery to terminate the process with SIGKILL. Signed-off-by: longjin <longjin@dragonos.org>
1 parent 4e18516 commit 1785ca3

1 file changed

Lines changed: 75 additions & 9 deletions

File tree

user/apps/tests/dunitest/suites/normal/spawn_exec_pipe_race.cc

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <time.h>
1616
#include <unistd.h>
1717

18+
#include <chrono>
1819
#include <string>
1920
#include <vector>
2021

@@ -155,6 +156,7 @@ void* SiblingExecThread(void*) {
155156

156157
struct FatalExecArgs {
157158
int tid_fd;
159+
int release_fd;
158160
};
159161

160162
void* FatalSiblingExecThread(void* opaque) {
@@ -163,7 +165,17 @@ void* FatalSiblingExecThread(void* opaque) {
163165
if (write(args->tid_fd, &tid, sizeof(tid)) != sizeof(tid)) {
164166
_exit(5);
165167
}
166-
return SiblingExecThread(nullptr);
168+
169+
char ready_fd[16];
170+
char release_fd[16];
171+
snprintf(ready_fd, sizeof(ready_fd), "%d", args->tid_fd);
172+
snprintf(release_fd, sizeof(release_fd), "%d", args->release_fd);
173+
char arg0[] = "/proc/self/exe";
174+
char* const argv[] = {arg0, const_cast<char*>(kExecBlockMode), ready_fd,
175+
release_fd, nullptr};
176+
char* const envp[] = {nullptr};
177+
execve("/proc/self/exe", argv, envp);
178+
_exit(errno);
167179
}
168180

169181
struct BlockingExecArgs {
@@ -434,15 +446,24 @@ TEST(SpawnExecPipeRace, FatalSignalDuringSiblingExecKeepsWaitOwnership) {
434446
for (int i = 0; i < kFatalIterations; ++i) {
435447
ChildCleanup cleanup;
436448
int tid_pipe[2] = {-1, -1};
449+
int release_pipe[2] = {-1, -1};
437450
ASSERT_EQ(0, pipe(tid_pipe)) << "iter=" << i << ": " << strerror(errno);
438451
cleanup.ready_read = tid_pipe[0];
439452
cleanup.ready_write = tid_pipe[1];
453+
ASSERT_EQ(0, pipe(release_pipe))
454+
<< "iter=" << i << ": " << strerror(errno);
455+
cleanup.release_read = release_pipe[0];
456+
cleanup.release_write = release_pipe[1];
440457

441458
pid_t child = fork();
442459
ASSERT_GE(child, 0) << "iter=" << i << ": " << strerror(errno);
443460
if (child == 0) {
444461
close(tid_pipe[0]);
445-
FatalExecArgs args = {.tid_fd = tid_pipe[1]};
462+
close(release_pipe[1]);
463+
FatalExecArgs args = {
464+
.tid_fd = tid_pipe[1],
465+
.release_fd = release_pipe[0],
466+
};
446467
pthread_t thread;
447468
if (pthread_create(&thread, nullptr, FatalSiblingExecThread, &args) != 0) {
448469
_exit(6);
@@ -455,6 +476,8 @@ TEST(SpawnExecPipeRace, FatalSignalDuringSiblingExecKeepsWaitOwnership) {
455476

456477
close(tid_pipe[1]);
457478
cleanup.ready_write = -1;
479+
close(release_pipe[0]);
480+
cleanup.release_read = -1;
458481
pid_t exec_tid = -1;
459482
ASSERT_EQ(static_cast<ssize_t>(sizeof(exec_tid)),
460483
read(tid_pipe[0], &exec_tid, sizeof(exec_tid)))
@@ -471,17 +494,60 @@ TEST(SpawnExecPipeRace, FatalSignalDuringSiblingExecKeepsWaitOwnership) {
471494
errno = 0;
472495
int kill_result = static_cast<int>(
473496
syscall(SYS_tgkill, child, exec_tid, SIGKILL));
474-
ASSERT_TRUE(kill_result == 0 || errno == ESRCH)
475-
<< "iter=" << i << ": " << strerror(errno);
497+
const int kill_errno = errno;
498+
ASSERT_TRUE(kill_result == 0 ||
499+
(kill_result == -1 && kill_errno == ESRCH))
500+
<< "iter=" << i << ": " << strerror(kill_errno);
501+
502+
bool target_retired = kill_result == -1;
503+
if (target_retired) {
504+
close(release_pipe[1]);
505+
cleanup.release_write = -1;
506+
}
476507

477508
int status = 0;
478-
ASSERT_TRUE(WaitForChildExit(child, &status, 5000))
509+
bool child_exited = false;
510+
auto deadline = std::chrono::steady_clock::now() +
511+
std::chrono::milliseconds(5000);
512+
while (std::chrono::steady_clock::now() < deadline) {
513+
pid_t waited = waitpid(child, &status, WNOHANG);
514+
if (waited == -1 && errno == EINTR) {
515+
continue;
516+
}
517+
ASSERT_TRUE(waited == 0 || waited == child)
518+
<< "iter=" << i << ": " << strerror(errno);
519+
if (waited == child) {
520+
child_exited = true;
521+
break;
522+
}
523+
524+
if (!target_retired) {
525+
errno = 0;
526+
int probe_result = static_cast<int>(
527+
syscall(SYS_tgkill, child, exec_tid, 0));
528+
const int probe_errno = errno;
529+
ASSERT_TRUE(probe_result == 0 ||
530+
(probe_result == -1 && probe_errno == ESRCH))
531+
<< "iter=" << i << ": " << strerror(probe_errno);
532+
if (probe_result == -1) {
533+
target_retired = true;
534+
close(release_pipe[1]);
535+
cleanup.release_write = -1;
536+
}
537+
}
538+
SleepForMillis(10);
539+
}
540+
541+
ASSERT_TRUE(child_exited)
479542
<< "fatal sibling exec was not waitable at iter=" << i;
480543
cleanup.child = -1;
481-
ASSERT_TRUE(WIFSIGNALED(status) ||
482-
(WIFEXITED(status) &&
483-
(WEXITSTATUS(status) == 0 || WEXITSTATUS(status) == EAGAIN)))
484-
<< "unexpected child status=" << status << " at iter=" << i;
544+
if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) {
545+
continue;
546+
}
547+
ASSERT_TRUE(target_retired && WIFEXITED(status) &&
548+
WEXITSTATUS(status) == 3)
549+
<< "thread-targeted SIGKILL was lost: child status=" << status
550+
<< " at iter=" << i;
485551
}
486552
}
487553

0 commit comments

Comments
 (0)