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