Skip to content
File

Blob: src/workerd/tests/libreprl/libreprl.c

c638 lines
1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4// Copyright 2019 Google LLC
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// https://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17 
18#ifndef _WIN32
19 
20#ifndef _GNU_SOURCE
21#define _GNU_SOURCE
22#endif
23 
24#include "libreprl.h"
25 
26#include <assert.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <poll.h>
30#include <signal.h>
31#include <stdarg.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sched.h>
36#include <sys/mman.h>
37#include <sys/mount.h>
38#include <sys/resource.h>
39#include <sys/time.h>
40#include <sys/stat.h>
41#include <sys/types.h>
42#include <sys/wait.h>
43#include <time.h>
44#include <unistd.h>
45 
46// Well-known file descriptor numbers for reprl <-> child communication, child process side
47#define REPRL_CHILD_CTRL_IN 100
48#define REPRL_CHILD_CTRL_OUT 101
49#define REPRL_CHILD_DATA_IN 102
50#define REPRL_CHILD_DATA_OUT 103
51 
52/// Maximum timeout in microseconds. Mostly just limited by the fact that the timeout in milliseconds has to fit into a 32-bit integer.
53#define REPRL_MAX_TIMEOUT_IN_MICROSECONDS ((uint64_t)(INT_MAX) * 1000)
54 
55static size_t min(size_t x, size_t y) {
56 return x < y ? x : y;
57}
58 
59#ifdef __linux__
60// This function creates the UID/GID mapping that we need inside of the user
61// namespace. This is needed such that the files we create have a proper owner
62// attached to them.
63static void write_id_maps(uid_t uid, gid_t gid) {
64 char setgroups_path[] = "/proc/self/setgroups";
65 char uid_map_path[] = "/proc/self/uid_map";
66 char gid_map_path[] = "/proc/self/gid_map";
67 
68 int setgroups_fd = open(setgroups_path, O_WRONLY);
69 int uid_map_fd = open(uid_map_path, O_WRONLY);
70 int gid_map_fd = open(gid_map_path, O_WRONLY);
71 
72 if (setgroups_fd == -1 || uid_map_fd == -1 || gid_map_fd == -1) {
73 fprintf(stderr, "Error opening setgroups/uid_map/gid_map file: %s\n", strerror(errno));
74 _exit(-1);
75 }
76 
77 // More context on this: https://lwn.net/Articles/626665/
78 dprintf(setgroups_fd, "deny");
79 dprintf(uid_map_fd, "%d %d 1", uid, uid);
80 dprintf(gid_map_fd, "%d %d 1", gid, gid);
81 
82 close(setgroups_fd);
83 close(uid_map_fd);
84 close(gid_map_fd);
85}
86 
87// Creates a tmpfs at `mount_point` in a new user namespace.
88static void create_tmpfs(const char* mount_point) {
89 // Get the UID and GID before we call unshare.
90 uid_t uid = getuid();
91 gid_t gid = getgid();
92 
93 // We create a new user (CLONE_NEWUSER) and mount (CLONE_NEWNS)
94 // namespace here such that we can mount our own tmpfs onto
95 // mount_point that is only visible to this process.
96 if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == -1) {
97 fprintf(stderr, "unshare failed to create a new mount namespace in the child: %s\n", strerror(errno));
98 _exit(-1);
99 };
100 
101 // Now write the UID / GID mappings
102 write_id_maps(uid, gid);
103 
104 // Mount a new tmpfs onto `mount_point` this allows us to add files
105 // here that get automatically cleaned up once the process exits.
106 if (mount("tmpfs", mount_point, "tmpfs", 0, nullptr) == -1) {
107 fprintf(stderr, "mount failed to create a tmpfs in namespace in the child: %s\n", strerror(errno));
108 _exit(-1);
109 }
110}
111#endif
112 
113static uint64_t current_usecs()
114{
115 struct timespec ts;
116 clock_gettime(CLOCK_MONOTONIC, &ts);
117 return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
118}
119 
120static char** copy_string_array(const char** orig)
121{
122 size_t num_entries = 0;
123 for (const char** current = orig; *current; current++) {
124 num_entries += 1;
125 }
126 char** copy = (char**) calloc(num_entries + 1, sizeof(char*));
127 for (size_t i = 0; i < num_entries; i++) {
128 copy[i] = strdup(orig[i]);
129 }
130 return copy;
131}
132 
133static void free_string_array(char** arr)
134{
135 if (!arr) return;
136 for (char** current = arr; *current; current++) {
137 free(*current);
138 }
139 free(arr);
140}
141 
142// A unidirectional communication channel for larger amounts of data, up to a maximum size (REPRL_MAX_DATA_SIZE).
143// Implemented as a (RAM-backed) file for which the file descriptor is shared with the child process and which is mapped into our address space.
144struct data_channel {
145 // File descriptor of the underlying file. Directly shared with the child process.
146 int fd;
147 // Memory mapping of the file, always of size REPRL_MAX_DATA_SIZE.
148 char* mapping;
149};
150 
151struct reprl_context {
152 // Whether reprl_initialize has been successfully performed on this context.
153 int initialized;
154 
155 // Read file descriptor of the control pipe. Only valid if a child process is running (i.e. pid is nonzero).
156 int ctrl_in;
157 // Write file descriptor of the control pipe. Only valid if a child process is running (i.e. pid is nonzero).
158 int ctrl_out;
159 
160 // Data channel REPRL -> Child
161 struct data_channel* data_in;
162 // Data channel Child -> REPRL
163 struct data_channel* data_out;
164 
165 // Optional data channel for the child's stdout and stderr.
166 struct data_channel* child_stdout;
167 struct data_channel* child_stderr;
168 
169 // PID of the child process. Will be zero if no child process is currently running.
170 pid_t pid;
171 
172 // Arguments and environment for the child process.
173 char** argv;
174 char** envp;
175 
176 // A malloc'd string containing a description of the last error that occurred.
177 char* last_error;
178};
179 
180static int reprl_error(struct reprl_context* ctx, const char *format, ...)
181{
182 va_list args;
183 va_start(args, format);
184 free(ctx->last_error);
185 vasprintf(&ctx->last_error, format, args);
186 return -1;
187}
188 
189static struct data_channel* reprl_create_data_channel(struct reprl_context* ctx)
190{
191#ifdef __linux__
192 int fd = memfd_create("REPRL_DATA_CHANNEL", MFD_CLOEXEC);
193#else
194 char path[] = "/tmp/reprl_data_channel_XXXXXXXX";
195 int fd = mkostemp(path, O_CLOEXEC);
196 unlink(path);
197#endif
198 if (fd == -1 || ftruncate(fd, REPRL_MAX_DATA_SIZE) != 0) {
199 reprl_error(ctx, "Failed to create data channel file: %s", strerror(errno));
200 return nullptr;
201 }
202 char* mapping = (char*) mmap(nullptr, REPRL_MAX_DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
203 if (mapping == MAP_FAILED) {
204 reprl_error(ctx, "Failed to mmap data channel file: %s", strerror(errno));
205 return nullptr;
206 }
207 
208 struct data_channel* channel = (struct data_channel*) malloc(sizeof(struct data_channel));
209 channel->fd = fd;
210 channel->mapping = mapping;
211 return channel;
212}
213 
214static void reprl_destroy_data_channel(struct data_channel* channel)
215{
216 if (!channel) return;
217 close(channel->fd);
218 munmap(channel->mapping, REPRL_MAX_DATA_SIZE);
219 free(channel);
220}
221 
222static void reprl_child_terminated(struct reprl_context* ctx)
223{
224 if (!ctx->pid) return;
225 ctx->pid = 0;
226 close(ctx->ctrl_in);
227 close(ctx->ctrl_out);
228}
229 
230static void reprl_terminate_child(struct reprl_context* ctx)
231{
232 if (!ctx->pid) return;
233 int status;
234 kill(ctx->pid, SIGKILL);
235 waitpid(ctx->pid, &status, 0);
236 reprl_child_terminated(ctx);
237}
238 
239static int reprl_spawn_child(struct reprl_context* ctx)
240{
241 // This is also a good time to ensure the data channel backing files don't grow too large.
242 ftruncate(ctx->data_in->fd, REPRL_MAX_DATA_SIZE);
243 ftruncate(ctx->data_out->fd, REPRL_MAX_DATA_SIZE);
244 if (ctx->child_stdout) ftruncate(ctx->child_stdout->fd, REPRL_MAX_DATA_SIZE);
245 if (ctx->child_stderr) ftruncate(ctx->child_stderr->fd, REPRL_MAX_DATA_SIZE);
246 
247 int crpipe[2] = { 0, 0 }; // control pipe child -> reprl
248 int cwpipe[2] = { 0, 0 }; // control pipe reprl -> child
249 
250 if (pipe(crpipe) != 0) {
251 return reprl_error(ctx, "Could not create pipe for REPRL communication: %s", strerror(errno));
252 }
253 if (pipe(cwpipe) != 0) {
254 close(crpipe[0]);
255 close(crpipe[1]);
256 return reprl_error(ctx, "Could not create pipe for REPRL communication: %s", strerror(errno));
257 }
258 
259 ctx->ctrl_in = crpipe[0];
260 ctx->ctrl_out = cwpipe[1];
261 fcntl(ctx->ctrl_in, F_SETFD, FD_CLOEXEC);
262 fcntl(ctx->ctrl_out, F_SETFD, FD_CLOEXEC);
263 
264#ifdef __linux__
265 // This is where we will mount our own tmpfs, this is intended to be used
266 // for targets like Chrome, where we have to pass the user data directory.
267 // Even if the target does not clean up after themselves, the tmpfs in the
268 // user namespace will be removed once the process exits. Also, every child
269 // process, i.e. fuzzing instance, can then have it's own tmpfs.
270 // This only works on Linux right now, which is where we fuzz Chrome, this
271 // won't work on any other OS.
272 const char mount_point[] = "/tmp/fuzzilli_tmp";
273 
274 // Create the mountpoint for our tmpfs here. This is just an empty dir.
275 // We also do not really care if this directory exists, we just need it as
276 // a mountpoint.
277 if (mkdir(mount_point, 0)) {
278 if (errno != EEXIST) {
279 fprintf(stderr, "mkdir failed to create %s to create a mountpoint: %s\n", mount_point, strerror(errno));
280 }
281 }
282#endif
283 
284#ifdef __linux__
285 // Use vfork() on Linux as that considerably improves the fuzzer performance. See also https://github.com/googleprojectzero/fuzzilli/issues/174
286 // Due to vfork, the code executed in the child process *must not* modify any memory apart from its stack, as it will share the page table of its parent.
287 pid_t pid = vfork();
288#else
289 pid_t pid = fork();
290#endif
291 if (pid == 0) {
292 if (dup2(cwpipe[0], REPRL_CHILD_CTRL_IN) < 0 ||
293 dup2(crpipe[1], REPRL_CHILD_CTRL_OUT) < 0 ||
294 dup2(ctx->data_out->fd, REPRL_CHILD_DATA_IN) < 0 ||
295 dup2(ctx->data_in->fd, REPRL_CHILD_DATA_OUT) < 0) {
296 fprintf(stderr, "dup2 failed in the child: %s\n", strerror(errno));
297 _exit(-1);
298 }
299 
300#ifdef __linux__
301 // Set RLIMIT_CORE to 0, such that we don't produce core dumps. The
302 // added benefit of doing this here, in the child process, is that we
303 // can still get core dumps when Fuzzilli crashes.
304 struct rlimit core_limit;
305 core_limit.rlim_cur = 0;
306 core_limit.rlim_max = 0;
307 if (setrlimit(RLIMIT_CORE, &core_limit) < 0) {
308 fprintf(stderr, "setrlimit failed in the child: %s\n", strerror(errno));
309 _exit(-1);
310 };
311#endif
312 
313 // Unblock any blocked signals. It seems that libdispatch sometimes blocks delivery of certain signals.
314 sigset_t newset;
315 sigemptyset(&newset);
316 if (sigprocmask(SIG_SETMASK, &newset, nullptr) != 0) {
317 fprintf(stderr, "sigprocmask failed in the child: %s\n", strerror(errno));
318 _exit(-1);
319 }
320 
321 close(cwpipe[0]);
322 close(crpipe[1]);
323 
324 int devnull = open("/dev/null", O_RDWR);
325 dup2(devnull, 0);
326 if (ctx->child_stdout) dup2(ctx->child_stdout->fd, 1);
327 else dup2(devnull, 1);
328 if (ctx->child_stderr) dup2(ctx->child_stderr->fd, 2);
329 else dup2(devnull, 2);
330 close(devnull);
331 
332#ifdef __linux__
333 // Create the tmpfs at the specific mount point here in the child process
334 // such that we have a tmpfs for this process only that will be cleaned up at process exit.
335 // This will also write into the necessary files in /proc, so we need to do this here after we've fork()'ed.
336 // This will only work on Linux, see the comment above where call mkdir.
337 create_tmpfs(mount_point);
338#endif
339 
340 // close all other FDs. We try to use FD_CLOEXEC everywhere, but let's be extra sure we don't leak any fds to the child.
341 int tablesize = getdtablesize();
342 // Cap at reasonable limit - getdtablesize() can return RLIM_INFINITY which is huge
343 if (tablesize > 1024 || tablesize < 0) {
344 tablesize = 1024;
345 }
346 for (int i = 3; i < tablesize; i++) {
347 if (i == REPRL_CHILD_CTRL_IN || i == REPRL_CHILD_CTRL_OUT || i == REPRL_CHILD_DATA_IN || i == REPRL_CHILD_DATA_OUT) {
348 continue;
349 }
350 close(i);
351 }
352 
353 execve(ctx->argv[0], ctx->argv, ctx->envp);
354 
355 fprintf(stderr, "Failed to execute child process %s: %s\n", ctx->argv[0], strerror(errno));
356 fflush(stderr);
357 _exit(-1);
358 }
359 
360 close(crpipe[1]);
361 close(cwpipe[0]);
362 
363 if (pid < 0) {
364 close(ctx->ctrl_in);
365 close(ctx->ctrl_out);
366 return reprl_error(ctx, "Failed to fork: %s", strerror(errno));
367 }
368 ctx->pid = pid;
369 
370 char helo[5] = { 0 };
371 if (read(ctx->ctrl_in, helo, 4) != 4) {
372 reprl_terminate_child(ctx);
373 return reprl_error(ctx, "Did not receive HELO message from child: %s", strerror(errno));
374 }
375 
376 if (strncmp(helo, "HELO", 4) != 0) {
377 reprl_terminate_child(ctx);
378 return reprl_error(ctx, "Received invalid HELO message from child: %s", helo);
379 }
380 
381 if (write(ctx->ctrl_out, helo, 4) != 4) {
382 reprl_terminate_child(ctx);
383 return reprl_error(ctx, "Failed to send HELO reply message to child: %s", strerror(errno));
384 }
385 
386#ifdef __linux__
387 struct rlimit core_limit = {};
388 if (prlimit(pid, RLIMIT_CORE, nullptr, &core_limit) < 0) {
389 reprl_terminate_child(ctx);
390 return reprl_error(ctx, "prlimit failed: %s\n", strerror(errno));
391 }
392 if (core_limit.rlim_cur != 0 || core_limit.rlim_max != 0) {
393 reprl_terminate_child(ctx);
394 return reprl_error(ctx, "Detected non-zero RLIMIT_CORE. Check that the child does not set RLIMIT_CORE manually.\n");
395 }
396#endif
397 
398 return 0;
399}
400 
401struct reprl_context* reprl_create_context()
402{
403 // "Reserve" the well-known REPRL fds so no other fd collides with them.
404 // This would cause various kinds of issues in reprl_spawn_child.
405 // It would be enough to do this once per process in the case of multiple
406 // REPRL instances, but it's probably not worth the implementation effort.
407 int devnull = open("/dev/null", O_RDWR);
408 dup2(devnull, REPRL_CHILD_CTRL_IN);
409 dup2(devnull, REPRL_CHILD_CTRL_OUT);
410 dup2(devnull, REPRL_CHILD_DATA_IN);
411 dup2(devnull, REPRL_CHILD_DATA_OUT);
412 close(devnull);
413 
414 return (struct reprl_context*) calloc(1, sizeof(struct reprl_context));
415}
416 
417int reprl_initialize_context(struct reprl_context* ctx, const char** argv, const char** envp, int capture_stdout, int capture_stderr)
418{
419 if (ctx->initialized) {
420 return reprl_error(ctx, "Context is already initialized");
421 }
422 
423 // We need to ignore SIGPIPE since we could end up writing to a pipe after our child process has exited.
424 signal(SIGPIPE, SIG_IGN);
425 
426 ctx->argv = copy_string_array(argv);
427 ctx->envp = copy_string_array(envp);
428 
429 ctx->data_in = reprl_create_data_channel(ctx);
430 ctx->data_out = reprl_create_data_channel(ctx);
431 if (capture_stdout) {
432 ctx->child_stdout = reprl_create_data_channel(ctx);
433 }
434 if (capture_stderr) {
435 ctx->child_stderr = reprl_create_data_channel(ctx);
436 }
437 if (!ctx->data_in || !ctx->data_out || (capture_stdout && !ctx->child_stdout) || (capture_stderr && !ctx->child_stderr)) {
438 // Proper error message will have been set by reprl_create_data_channel
439 return -1;
440 }
441 
442 ctx->initialized = 1;
443 return 0;
444}
445 
446void reprl_destroy_context(struct reprl_context* ctx)
447{
448 reprl_terminate_child(ctx);
449 
450 free_string_array(ctx->argv);
451 free_string_array(ctx->envp);
452 
453 reprl_destroy_data_channel(ctx->data_in);
454 reprl_destroy_data_channel(ctx->data_out);
455 reprl_destroy_data_channel(ctx->child_stdout);
456 reprl_destroy_data_channel(ctx->child_stderr);
457 
458 free(ctx->last_error);
459 free(ctx);
460}
461 
462int reprl_execute(struct reprl_context* ctx, const char* script, uint64_t script_size, uint64_t timeout, uint64_t* execution_time, int fresh_instance)
463{
464 if (!ctx->initialized) {
465 return reprl_error(ctx, "REPRL context is not initialized");
466 }
467 
468 if (script_size > REPRL_MAX_DATA_SIZE) {
469 return reprl_error(ctx, "Script too large");
470 }
471 
472 if (timeout > REPRL_MAX_TIMEOUT_IN_MICROSECONDS) {
473 return reprl_error(ctx, "Timeout too large");
474 }
475 int timeout_ms = (int)(timeout / 1000);
476 
477 // Terminate any existing instance if requested.
478 if (fresh_instance && ctx->pid) {
479 reprl_terminate_child(ctx);
480 }
481 
482 // Reset file position so the child can simply read(2) and write(2) to these fds.
483 lseek(ctx->data_out->fd, 0, SEEK_SET);
484 lseek(ctx->data_in->fd, 0, SEEK_SET);
485 if (ctx->child_stdout) {
486 lseek(ctx->child_stdout->fd, 0, SEEK_SET);
487 }
488 if (ctx->child_stderr) {
489 lseek(ctx->child_stderr->fd, 0, SEEK_SET);
490 }
491 
492 // Spawn a new instance if necessary.
493 if (!ctx->pid) {
494 int r = reprl_spawn_child(ctx);
495 if (r != 0) return r;
496 }
497 
498 // Copy the script to the data channel.
499 memcpy(ctx->data_out->mapping, script, script_size);
500 
501 fprintf(stderr, "[libreprl] About to write 'exec' command to child (ctrl_out fd=%d, pid=%d)\n",
502 ctx->ctrl_out, ctx->pid);
503 fflush(stderr);
504 
505 // Tell child to execute the script.
506 ssize_t write1 = write(ctx->ctrl_out, "exec", 4);
507 ssize_t write2 = write(ctx->ctrl_out, &script_size, 8);
508 
509 fprintf(stderr, "[libreprl] Write results: exec=%zd (expected 4), script_size=%zd (expected 8)\n",
510 write1, write2);
511 fflush(stderr);
512 
513 if (write1 != 4 || write2 != 8) {
514 // These can fail if the child unexpectedly terminated between executions.
515 // Check for that here to be able to provide a better error message.
516 int status;
517 if (waitpid(ctx->pid, &status, WNOHANG) == ctx->pid) {
518 reprl_child_terminated(ctx);
519 if (WIFEXITED(status)) {
520 return reprl_error(ctx, "Child unexpectedly exited with status %i between executions", WEXITSTATUS(status));
521 } else {
522 return reprl_error(ctx, "Child unexpectedly terminated with signal %i between executions", WTERMSIG(status));
523 }
524 }
525 return reprl_error(ctx, "Failed to send command to child process: %s", strerror(errno));
526 }
527 
528 fprintf(stderr, "[libreprl] Command written successfully, now polling for response (ctrl_in fd=%d, timeout=%dms)\n",
529 ctx->ctrl_in, timeout_ms);
530 fflush(stderr);
531 
532 // Check if child is still alive before polling
533 int check_status;
534 pid_t check = waitpid(ctx->pid, &check_status, WNOHANG);
535 if (check == ctx->pid) {
536 fprintf(stderr, "[libreprl] WARNING: Child died before poll! status=%d\n", check_status);
537 fflush(stderr);
538 } else if (check < 0) {
539 fprintf(stderr, "[libreprl] WARNING: waitpid check failed: %s\n", strerror(errno));
540 fflush(stderr);
541 } else {
542 fprintf(stderr, "[libreprl] Child still alive (pid=%d), proceeding with poll\n", ctx->pid);
543 fflush(stderr);
544 }
545 
546 // Wait for child to finish execution (or crash).
547 uint64_t start_time = current_usecs();
548 struct pollfd fds = {.fd = ctx->ctrl_in, .events = POLLIN, .revents = 0};
549 fprintf(stderr, "[libreprl] Calling poll...\n");
550 fflush(stderr);
551 int res = poll(&fds, 1, timeout_ms);
552 fprintf(stderr, "[libreprl] poll() returned %d (revents=0x%x)\n", res, fds.revents);
553 fflush(stderr);
554 *execution_time = current_usecs() - start_time;
555 if (res == 0) {
556 // Execution timed out. Kill child and return a timeout status.
557 reprl_terminate_child(ctx);
558 return 1 << 16;
559 } else if (res != 1) {
560 // An error occurred.
561 // We expect all signal handlers to be installed with SA_RESTART, so receiving EINTR here is unexpected and thus also an error.
562 return reprl_error(ctx, "Failed to poll: %s", strerror(errno));
563 }
564 
565 // Poll succeeded, so there must be something to read now (either the status or EOF).
566 int status;
567 ssize_t rv = read(ctx->ctrl_in, &status, 4);
568 if (rv < 0) {
569 return reprl_error(ctx, "Failed to read from control pipe: %s", strerror(errno));
570 } else if (rv != 4) {
571 // Most likely, the child process crashed and closed the write end of the control pipe.
572 // Unfortunately, there probably is nothing that guarantees that waitpid() will immediately succeed now,
573 // and we also don't want to block here. So just retry waitpid() a few times...
574 int success = 0;
575 do {
576 success = waitpid(ctx->pid, &status, WNOHANG) == ctx->pid;
577 if (!success) usleep(10);
578 } while (!success && current_usecs() - start_time < timeout);
579 
580 if (!success) {
581 // Wait failed, so something weird must have happened. Maybe somehow the control pipe was closed without the child exiting?
582 // Probably the best we can do is kill the child and return an error.
583 reprl_terminate_child(ctx);
584 return reprl_error(ctx, "Child in weird state after execution");
585 }
586 
587 // Cleanup any state related to this child process.
588 reprl_child_terminated(ctx);
589 
590 if (WIFEXITED(status)) {
591 status = WEXITSTATUS(status) << 8;
592 } else if (WIFSIGNALED(status)) {
593 status = WTERMSIG(status);
594 } else {
595 // This shouldn't happen, since we don't specify WUNTRACED for waitpid...
596 return reprl_error(ctx, "Waitpid returned unexpected child state %i", status);
597 }
598 }
599 
600 // The status must be a positive number, see the status encoding format below.
601 // We also don't allow the child process to indicate a timeout. If we wanted,
602 // we could treat it as an error if the upper bits are set.
603 status &= 0xffff;
604 
605 return status;
606}
607 
608static const char* fetch_data_channel_content(struct data_channel* channel)
609{
610 if (!channel) return "";
611 size_t pos = lseek(channel->fd, 0, SEEK_CUR);
612 pos = min(pos, REPRL_MAX_DATA_SIZE - 1);
613 channel->mapping[pos] = 0;
614 return channel->mapping;
615}
616 
617const char* reprl_fetch_fuzzout(struct reprl_context* ctx)
618{
619 return fetch_data_channel_content(ctx->data_in);
620}
621 
622const char* reprl_fetch_stdout(struct reprl_context* ctx)
623{
624 return fetch_data_channel_content(ctx->child_stdout);
625}
626 
627const char* reprl_fetch_stderr(struct reprl_context* ctx)
628{
629 return fetch_data_channel_content(ctx->child_stderr);
630}
631 
632const char* reprl_get_last_error(struct reprl_context* ctx)
633{
634 return ctx->last_error;
635}
636 
637#endif