Serenity Operating System
1/*
2 * Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Assertions.h>
8#include <AK/Format.h>
9#include <pthread.h>
10#include <signal.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <unistd.h>
14
15/*
16 * Bug:
17 * If the main thread of a process is no longer alive, it cannot receive
18 * signals anymore. This can manifest as, for example, an unkillable process.
19 *
20 * So what needs to happen:
21 * - There is process P
22 * - It has more than one thread
23 * - The main thread calls thread_exit(), leaving the rest of the threads alive
24 * - Now the process is unkillable!
25 *
26 * Here's how to demonstrate the bug:
27 * - Time 0: PX forks into PZ (mnemonic: Zombie)
28 * - Time 1: PZ's main thread T1 creates a new thread T2
29 * - Time 2: Nothing (T2 could communicate to PX both process and thread ID)
30 * (most LibC functions crash currently, which is a different bug I suppose.)
31 * - Time 3: T1 calls thread_exit()
32 * - Time 4:
33 * * PX tries to kill PZ (should work, but doesn't)
34 * * PX tries to kill PZ using T2's thread ID (shouldn't work, and doesn't)
35 * * PX outputs all results.
36 */
37
38static constexpr useconds_t STEP_SIZE = 1100000;
39
40static void fork_into(void(fn)())
41{
42 const pid_t rc = fork();
43 if (rc < 0) {
44 perror("fork");
45 exit(1);
46 }
47 if (rc > 0) {
48 return;
49 }
50 fn();
51 dbgln("child finished (?)");
52 exit(1);
53}
54
55static void thread_into(void* (*fn)(void*))
56{
57 pthread_t tid;
58 int const rc = pthread_create(&tid, nullptr, fn, nullptr);
59 if (rc < 0) {
60 perror("pthread_create");
61 exit(1);
62 }
63}
64
65static void sleep_steps(useconds_t steps)
66{
67 int const rc = usleep(steps * STEP_SIZE);
68 if (rc < 0) {
69 perror("usleep");
70 VERIFY_NOT_REACHED();
71 }
72}
73
74static bool try_kill(pid_t kill_id)
75{
76 int rc = kill(kill_id, SIGTERM);
77 perror("kill");
78 printf("kill rc: %d\n", rc);
79 return rc >= 0;
80}
81
82static void run_pz();
83static void* run_pz_t2_wrap(void* fd_ptr);
84static void run_pz_t2();
85
86int main(int, char**)
87{
88 // This entire function is the entirety of process PX.
89
90 // Time 0: PX forks into PZ (mnemonic: Zombie)
91 dbgln("PX forks into PZ");
92 fork_into(run_pz);
93 sleep_steps(4);
94
95 // Time 4:
96 dbgln("Let's hope everything went fine!");
97 pid_t guessed_pid = getpid() + 1;
98 pid_t guessed_tid = guessed_pid + 1;
99 printf("About to kill PID %d, TID %d.\n", guessed_pid, guessed_tid);
100 if (try_kill(guessed_tid)) {
101 printf("FAIL, could kill a thread\n");
102 exit(1);
103 }
104 if (!try_kill(guessed_pid)) {
105 printf("FAIL, could not kill the process\n");
106 exit(1);
107 }
108
109 printf("PASS\n");
110 return 0;
111}
112
113static void run_pz()
114{
115 // Time 0: PX forks into PZ (mnemonic: Zombie)
116 sleep_steps(1);
117
118 // Time 1: PZ's main thread T1 creates a new thread T2
119 dbgln("PZ calls pthread_create");
120 thread_into(run_pz_t2_wrap);
121 sleep_steps(2);
122
123 // Time 3: T1 calls thread_exit()
124 dbgln("PZ(T1) calls thread_exit");
125 pthread_exit(nullptr);
126 VERIFY_NOT_REACHED();
127}
128
129static void* run_pz_t2_wrap(void*)
130{
131 run_pz_t2();
132 exit(1);
133}
134
135static void run_pz_t2()
136{
137 // Time 1: PZ's main thread T1 creates a new thread T2
138 sleep_steps(1);
139
140 // Time 2: Nothing
141 // FIXME: For some reason, both printf() and dbg() crash.
142 // This also prevents us from using a pipe to communicate to PX both process and thread ID
143 // dbgln("T2: I'm alive and well.");
144 sleep_steps(18);
145
146 // Time 20: Cleanup
147 printf("PZ(T2) dies from boredom.\n");
148 exit(0);
149}