Skip to content

Commit 495ecaf

Browse files
wan9chicodex
andcommitted
test(fspy): harden x86 gvisor rerun
Co-authored-by: GPT-5.6 Codex <codex@openai.com>
1 parent f136f3e commit 495ecaf

2 files changed

Lines changed: 116 additions & 33 deletions

File tree

.github/workflows/ptrace-gvisor-x86-experiment.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ jobs:
102102
docker run --rm --init --runtime=runsc --user 65534:65534 \
103103
fspy-ptrace-gvisor-x86-probe
104104
105-
- name: Require runc injector control and capture runsc injector outcome
105+
- name: Require runc control and runsc injector success
106106
run: |
107107
set -euxo pipefail
108108
set +e
@@ -125,6 +125,9 @@ jobs:
125125
set -e
126126
cat "target/ptrace-gvisor-x86/${label}.log"
127127
printf '%s_EXIT=%s\n' "$label" "$rc"
128+
test "$rc" -eq 0
129+
grep -F 'openat: test_path' "target/ptrace-gvisor-x86/${label}.log"
130+
grep -F '/bin/cat exited with code 0' "target/ptrace-gvisor-x86/${label}.log"
128131
}
129132
capture_runsc_inject RUNSC_X86_ROOT_INJECT \
130133
docker run --rm --init --runtime=runsc fspy-ptrace-gvisor-x86-inject
@@ -146,3 +149,11 @@ jobs:
146149
/usr/local/bin/gvisor-bin \
147150
/tmp/gvisor-release
148151
exit 0
152+
153+
- name: Verify temporary runsc runtime was removed
154+
if: always()
155+
run: |
156+
set -euxo pipefail
157+
test ! -e /usr/local/bin/runsc
158+
test ! -e /usr/local/bin/containerd-shim-runsc-v1
159+
! docker info --format 'Runtimes={{json .Runtimes}}' | grep -q '"runsc"'

research/ptrace-environment-probe.c

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,28 @@ static int required_exit_status(void) {
4040

4141
static void kill_and_reap(pid_t pid) {
4242
kill(pid, SIGKILL);
43-
while (waitpid(pid, NULL, 0) < 0 && errno == EINTR) {}
43+
for (;;) {
44+
int status;
45+
pid_t waited;
46+
do {
47+
waited = waitpid(pid, &status, __WALL);
48+
} while (waited < 0 && errno == EINTR);
49+
if (waited < 0 || WIFEXITED(status) || WIFSIGNALED(status)) return;
50+
if (WIFSTOPPED(status)) ptrace(PTRACE_CONT, pid, 0, (void *)(uintptr_t)SIGKILL);
51+
}
52+
}
53+
54+
static int interrupt_and_detach(pid_t pid) {
55+
if (ptrace(PTRACE_INTERRUPT, pid, 0, 0) < 0) return errno;
56+
int status = 0;
57+
pid_t waited;
58+
do {
59+
waited = waitpid(pid, &status, __WALL);
60+
} while (waited < 0 && errno == EINTR);
61+
if (waited < 0) return errno;
62+
if (waited != pid || !WIFSTOPPED(status)) return EPROTO;
63+
if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return errno;
64+
return 0;
4465
}
4566

4667
static pid_t paused_child(int *ready_fd, volatile uint64_t **remote_word) {
@@ -153,15 +174,39 @@ static void test_seize_and_vm_io(void) {
153174
report("process-vm-read", read_ok, read_error, "direct child");
154175

155176
if (seize_ok) {
156-
if (ptrace(PTRACE_INTERRUPT, pid, 0, 0) < 0) abort();
157-
int status;
158-
while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {}
159-
if (!WIFSTOPPED(status)) abort();
160-
177+
int word_error = 0;
178+
bool word_ok = true;
179+
bool stopped = false;
161180
errno = 0;
162-
long peeked = ptrace(PTRACE_PEEKDATA, pid, (void *)remote_word, 0);
163-
int word_error = errno;
164-
bool word_ok = !(peeked == -1 && word_error != 0);
181+
if (ptrace(PTRACE_INTERRUPT, pid, 0, 0) < 0) {
182+
word_error = errno;
183+
word_ok = false;
184+
}
185+
186+
if (word_ok) {
187+
int status = 0;
188+
pid_t waited;
189+
do {
190+
waited = waitpid(pid, &status, 0);
191+
} while (waited < 0 && errno == EINTR);
192+
if (waited < 0) {
193+
word_error = errno;
194+
word_ok = false;
195+
} else if (waited != pid || !WIFSTOPPED(status)) {
196+
word_error = EPROTO;
197+
word_ok = false;
198+
} else {
199+
stopped = true;
200+
}
201+
}
202+
203+
long peeked = 0;
204+
if (word_ok) {
205+
errno = 0;
206+
peeked = ptrace(PTRACE_PEEKDATA, pid, (void *)remote_word, 0);
207+
word_error = errno;
208+
word_ok = !(peeked == -1 && word_error != 0);
209+
}
165210
const uint64_t ptrace_replacement = UINT64_C(0x8877665544332211);
166211
if (word_ok &&
167212
ptrace(PTRACE_POKEDATA, pid, (void *)remote_word,
@@ -176,9 +221,12 @@ static void test_seize_and_vm_io(void) {
176221
word_ok = !(peeked == -1 && word_error != 0) &&
177222
(uint64_t)(unsigned long)peeked == ptrace_replacement;
178223
}
224+
if (stopped && ptrace(PTRACE_DETACH, pid, 0, 0) < 0) {
225+
if (word_ok) word_error = errno;
226+
word_ok = false;
227+
}
179228
report_required("ptrace-word-io", word_ok, word_error,
180-
"stopped direct child");
181-
if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) abort();
229+
"interrupt, stop, word I/O, and detach");
182230
} else {
183231
report_required("ptrace-word-io", false, error,
184232
"seize failed before word I/O");
@@ -189,14 +237,7 @@ static void test_seize_and_vm_io(void) {
189237
static int seize_from_child(pid_t target) {
190238
errno = 0;
191239
if (ptrace(PTRACE_SEIZE, target, 0, 0) < 0) return errno;
192-
if (ptrace(PTRACE_INTERRUPT, target, 0, 0) < 0) return errno;
193-
int status;
194-
while (waitpid(target, &status, __WALL) < 0) {
195-
if (errno != EINTR) return errno;
196-
}
197-
if (!WIFSTOPPED(status)) return EPROTO;
198-
if (ptrace(PTRACE_DETACH, target, 0, 0) < 0) return errno;
199-
return 0;
240+
return interrupt_and_detach(target);
200241
}
201242

202243
static void test_sibling(bool allow_with_prctl) {
@@ -238,9 +279,13 @@ static void test_sibling(bool allow_with_prctl) {
238279
}
239280
if (write(tracer_pipe[1], &target, sizeof(target)) != sizeof(target)) abort();
240281
close(tracer_pipe[1]);
241-
int status;
242-
while (waitpid(tracer, &status, 0) < 0 && errno == EINTR) {}
243-
int result = WIFEXITED(status) ? WEXITSTATUS(status) : 255;
282+
int status = 0;
283+
pid_t waited;
284+
do {
285+
waited = waitpid(tracer, &status, 0);
286+
} while (waited < 0 && errno == EINTR);
287+
int result = waited == tracer && WIFEXITED(status) ? WEXITSTATUS(status) : 255;
288+
if (waited < 0) result = errno;
244289
report(name, result == 0, result, allow_with_prctl ? "target opted in" : "same UID");
245290
kill_and_reap(target);
246291
}
@@ -263,8 +308,9 @@ static void test_dumpable_zero(void) {
263308
errno = 0;
264309
int rc = ptrace(PTRACE_SEIZE, pid, 0, 0);
265310
int error = errno;
266-
report("seize-dumpable-zero-child", rc == 0, error, "");
267-
if (rc == 0) ptrace(PTRACE_DETACH, pid, 0, 0);
311+
bool ok = rc == 0;
312+
if (ok && (error = interrupt_and_detach(pid)) != 0) ok = false;
313+
report("seize-dumpable-zero-child", ok, error, "");
268314
kill_and_reap(pid);
269315
}
270316

@@ -286,9 +332,10 @@ static void test_grandchild(void) {
286332
errno = 0;
287333
int rc = ptrace(PTRACE_SEIZE, target, 0, 0);
288334
int error = errno;
289-
report_required("seize-live-grandchild", rc == 0, error,
335+
bool ok = rc == 0;
336+
if (ok && (error = interrupt_and_detach(target)) != 0) ok = false;
337+
report_required("seize-live-grandchild", ok, error,
290338
"ancestor, not direct parent");
291-
if (rc == 0) ptrace(PTRACE_DETACH, target, 0, 0);
292339
kill_and_reap(target);
293340
kill_and_reap(middle);
294341
}
@@ -314,9 +361,10 @@ static void test_orphan(bool subreaper) {
314361
errno = 0;
315362
int rc = ptrace(PTRACE_SEIZE, target, 0, 0);
316363
int error = errno;
364+
bool ok = rc == 0;
365+
if (ok && (error = interrupt_and_detach(target)) != 0) ok = false;
317366
report(subreaper ? "seize-orphan-subreaper" : "seize-orphan-no-subreaper",
318-
rc == 0, error, subreaper ? "reparented to supervisor" : "reparented away");
319-
if (rc == 0) ptrace(PTRACE_DETACH, target, 0, 0);
367+
ok, error, subreaper ? "reparented to supervisor" : "reparented away");
320368
kill(target, SIGKILL);
321369
waitpid(target, NULL, 0);
322370
prctl(PR_SET_CHILD_SUBREAPER, 0, 0, 0, 0);
@@ -338,16 +386,40 @@ static void test_suid_exec(bool traced) {
338386
execl(suid_target, suid_target, "suid-target", NULL);
339387
_exit(121);
340388
}
341-
int status;
342-
waitpid(pid, &status, 0);
389+
int status = 0;
390+
pid_t waited;
391+
do {
392+
waited = waitpid(pid, &status, 0);
393+
} while (waited < 0 && errno == EINTR);
394+
if (waited < 0) {
395+
int error = errno;
396+
report_required(traced ? "setuid-exec-traced" : "setuid-exec-untraced",
397+
false, error, "waitpid failed");
398+
kill_and_reap(pid);
399+
return;
400+
}
343401
if (traced && WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
344-
ptrace(PTRACE_CONT, pid, 0, 0);
345-
waitpid(pid, &status, 0);
402+
if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) {
403+
int error = errno;
404+
report_required("setuid-exec-traced", false, error, "PTRACE_CONT failed");
405+
kill_and_reap(pid);
406+
return;
407+
}
408+
do {
409+
waited = waitpid(pid, &status, 0);
410+
} while (waited < 0 && errno == EINTR);
411+
if (waited < 0) {
412+
int error = errno;
413+
report_required("setuid-exec-traced", false, error, "waitpid failed");
414+
kill_and_reap(pid);
415+
return;
416+
}
346417
}
347418
int code = WIFEXITED(status) ? WEXITSTATUS(status) : 255;
348419
bool ok = traced ? code == 42 : code == 0;
349420
report_required(traced ? "setuid-exec-traced" : "setuid-exec-untraced", ok, 0,
350421
traced ? "expected euid unchanged" : "expected euid root");
422+
if (!WIFEXITED(status) && !WIFSIGNALED(status)) kill_and_reap(pid);
351423
}
352424

353425
static void print_proc_field(const char *prefix) {

0 commit comments

Comments
 (0)