Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to ShellClaw are documented here. Format follows [Keep a Cha
## [Unreleased]

### Fixed
- Sandboxed shell commands no longer inherit the agent environment: the command process keeps only `PATH`, locale (`LANG`, `LC_ALL`, `LC_CTYPE`), `TERM`, `TZ`, and `HOME` (set to the workspace), so a prompt-injected `printenv` cannot read provider API keys or bot tokens. `LD_*`, `CUDA_*`, and proxy variables are dropped before `exec`; see the environment model in `docs/SECURITY.md` (#109).
- The shell command timeout is enforced against a monotonic wall clock on both the sandboxed and the unsandboxed paths, so a command that keeps stdout trickling can no longer outlive `shell_timeout_sec` and hold the agent lock until the output buffer fills (#116).
- Telegram `getUpdates` requests a single update per poll and skips a message without a numeric sender id, so a backlog larger than the 256 KiB read buffer (or a channel/anonymous post) no longer pins the poll offset and stalls the inbox.
- Telegram `sendMessage` delivery is verified from the HTTP status code and the `ok` body field, so an API refusal (HTTP 400/429) is no longer reported as a successful send and acknowledged as delivered.
- Long Telegram and Discord replies are chunked to the platform limits (4096/2000 bytes, UTF-8 boundary-safe) through the shared `channel_send_chunked` helper, so a reply over the cap is delivered as several messages instead of failing the whole send; chunk posts are paced for the ~1 msg/s Telegram chat rate, and a failed mid-reply post logs its chunk index and byte offset.
Expand Down
23 changes: 17 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,12 @@ ASAP_INVOKE_TEST_O := $(BINDIR)/asap_invoke_test.o
# Sandbox (Phase 3 §5)
SANDBOX_CORE_O := src/sandbox/sandbox.o
SANDBOX_LANDLOCK_O := src/sandbox/sandbox_landlock.o
SANDBOX_O := $(SANDBOX_CORE_O) $(SANDBOX_LANDLOCK_O)
SANDBOX_ENV_O := src/sandbox/sandbox_env.o
# Sandbox unit test object: exposes drain_pipe helpers (SHELLCLAW_SANDBOX_TEST)
# so coverage does not depend on namespace privileges the CI runners revoke.
# Lives under src/sandbox/ so the coverage.sh capture (--directory src) merges it.
SANDBOX_TEST_O := src/sandbox/sandbox_test.o
SANDBOX_O := $(SANDBOX_CORE_O) $(SANDBOX_LANDLOCK_O) $(SANDBOX_ENV_O)
ALLOWLIST_SCAN_O := src/sandbox/allowlist.o
ALLOWLIST_PATH_O := src/sandbox/allowlist_path.o
ALLOWLIST_O := $(ALLOWLIST_SCAN_O) $(ALLOWLIST_PATH_O)
Expand Down Expand Up @@ -385,9 +390,15 @@ $(SHELL_O): src/tools/shell.c src/tools/tool.h src/tools/shell.h src/core/config
src/sandbox/sandbox.h src/sandbox/allowlist.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/tools/shell.c

$(SANDBOX_CORE_O): src/sandbox/sandbox.c src/sandbox/sandbox.h src/sandbox/sandbox_landlock.h
$(SANDBOX_CORE_O): src/sandbox/sandbox.c src/sandbox/sandbox.h src/sandbox/sandbox_landlock.h src/sandbox/sandbox_env.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/sandbox.c

$(SANDBOX_ENV_O): src/sandbox/sandbox_env.c src/sandbox/sandbox_env.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/sandbox_env.c

$(SANDBOX_TEST_O): src/sandbox/sandbox.c src/sandbox/sandbox.h src/sandbox/sandbox_landlock.h src/sandbox/sandbox_env.h
$(CC) $(CFLAGS) $(INC) -DSHELLCLAW_SANDBOX_TEST -c -o $@ src/sandbox/sandbox.c

$(SANDBOX_LANDLOCK_O): src/sandbox/sandbox_landlock.c src/sandbox/sandbox_landlock.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/sandbox_landlock.c

Expand Down Expand Up @@ -763,9 +774,9 @@ test_static: tests/test_static.c src/gateway/ui_assets.h src/gateway/static.o
$(CC) $(CFLAGS) $(LDFLAGS) $(INC) -o $(BINDIR)/$@ tests/test_static.c src/gateway/static.o $(LDLIBS)
$(DSYM_SCRIPT)

test_sandbox: tests/test_sandbox.c $(SANDBOX_O)
test_sandbox: tests/test_sandbox.c $(SANDBOX_TEST_O) $(SANDBOX_LANDLOCK_O) $(SANDBOX_ENV_O)
@mkdir -p $(BINDIR)
$(CC) $(CFLAGS) $(LDFLAGS) $(INC) -o $(BINDIR)/$@ tests/test_sandbox.c $(SANDBOX_O) $(LDLIBS)
$(CC) $(CFLAGS) $(LDFLAGS) $(INC) -o $(BINDIR)/$@ tests/test_sandbox.c $(SANDBOX_TEST_O) $(SANDBOX_LANDLOCK_O) $(SANDBOX_ENV_O) $(LDLIBS)
$(DSYM_SCRIPT)

test_allowlist: tests/test_allowlist.c $(ALLOWLIST_O)
Expand Down Expand Up @@ -923,9 +934,9 @@ clean-root-dsym:
@rm -f shellclaw test_agent test_anthropic test_channel test_cli test_config test_file test_memory test_local_provider test_openai test_provider test_router test_shell test_skill test_telegram test_web_search test_ws

clean: clean-root-dsym
rm -f $(OBJS) $(PROVIDER_COMMON_O) $(STUB_O) $(ANTHROPIC_O) $(OPENAI_COMPAT_O) $(OPENAI_O) $(LOCAL_O) $(ROUTER_O) $(CJSON_O) $(TWEETNACL_O) $(ANTHROPIC_TEST_O) $(OPENAI_TEST_O) $(LOCAL_TEST_O) $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(CHANNEL_TG_TEST_O) $(CHANNEL_COMMON_O) $(CHANNEL_STUB_O) $(CHANNEL_CLI_O) $(CHANNEL_TG_O) $(CHANNEL_DISCORD_O) $(DISCORD_HELPERS_O) $(CHANNEL_HEARTBEAT_O) $(CHANNEL_WEBCHAT_O) $(AUTH_O) $(STATIC_O) $(HTTP_O) $(HTTP_LWS_O) $(ASAP_HTTP_BODY_O) $(ROUTES_O) $(ROUTES_HARDWARE_O) $(WS_O) $(MANIFEST_O) $(MANIFEST_PROFILES_O) $(MANIFEST_BUILD_O) $(MANIFEST_SIGN_O) $(MANIFEST_KEYS_O) $(ENVELOPE_O) $(ULID_O) $(CLIENT_O) $(ASAP_REGISTRY_O) $(SERVER_O) $(ASAP_LOG_O) $(RATE_LIMIT_O) $(SHELL_O) $(WEBSEARCH_O) $(FILE_O) $(REGISTRY_O) $(CONTEXT_O) $(CONTEXT_CACHE_O) $(CONTEXT_HTTP_O) $(CONTEXT_GEO_O) $(CRYPTO_O) $(JCS_O) $(HARDWARE_STUB_O) $(HARDWARE_INIT_O) $(HARDWARE_GPIO_SNAPSHOT_O) $(HARDWARE_TEGRASTATS_O) $(HARDWARE_TOOLS_O) $(BOARD_DETECT_O) src/hardware/hardware_libgpiod.o $(HARDWARE_I2C_O) $(HARDWARE_CAMERA_O) $(CRON_O) $(ASAP_INVOKE_O) $(SANDBOX_CORE_O) $(SANDBOX_LANDLOCK_O) $(ALLOWLIST_SCAN_O) $(ALLOWLIST_PATH_O)
rm -f $(OBJS) $(PROVIDER_COMMON_O) $(STUB_O) $(ANTHROPIC_O) $(OPENAI_COMPAT_O) $(OPENAI_O) $(LOCAL_O) $(ROUTER_O) $(CJSON_O) $(TWEETNACL_O) $(ANTHROPIC_TEST_O) $(OPENAI_TEST_O) $(LOCAL_TEST_O) $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(CHANNEL_TG_TEST_O) $(CHANNEL_COMMON_O) $(CHANNEL_STUB_O) $(CHANNEL_CLI_O) $(CHANNEL_TG_O) $(CHANNEL_DISCORD_O) $(DISCORD_HELPERS_O) $(CHANNEL_HEARTBEAT_O) $(CHANNEL_WEBCHAT_O) $(AUTH_O) $(STATIC_O) $(HTTP_O) $(HTTP_LWS_O) $(ASAP_HTTP_BODY_O) $(ROUTES_O) $(ROUTES_HARDWARE_O) $(WS_O) $(MANIFEST_O) $(MANIFEST_PROFILES_O) $(MANIFEST_BUILD_O) $(MANIFEST_SIGN_O) $(MANIFEST_KEYS_O) $(ENVELOPE_O) $(ULID_O) $(CLIENT_O) $(ASAP_REGISTRY_O) $(SERVER_O) $(ASAP_LOG_O) $(RATE_LIMIT_O) $(SHELL_O) $(WEBSEARCH_O) $(FILE_O) $(REGISTRY_O) $(CONTEXT_O) $(CONTEXT_CACHE_O) $(CONTEXT_HTTP_O) $(CONTEXT_GEO_O) $(CRYPTO_O) $(JCS_O) $(HARDWARE_STUB_O) $(HARDWARE_INIT_O) $(HARDWARE_GPIO_SNAPSHOT_O) $(HARDWARE_TEGRASTATS_O) $(HARDWARE_TOOLS_O) $(BOARD_DETECT_O) src/hardware/hardware_libgpiod.o $(HARDWARE_I2C_O) $(HARDWARE_CAMERA_O) $(CRON_O) $(ASAP_INVOKE_O) $(SANDBOX_CORE_O) $(SANDBOX_LANDLOCK_O) $(SANDBOX_ENV_O) $(ALLOWLIST_SCAN_O) $(ALLOWLIST_PATH_O)
rm -f src/gateway/ui_assets.h
find . -name '*.gcno' -o -name '*.gcda' -o -name '*.gcov' | xargs rm -f 2>/dev/null || true
rm -f $(WS_TEST_O) $(BINDIR)/asap_registry_test.o $(BINDIR)/asap_invoke_test.o $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(BINDIR)/shellclaw $(BINDIR)/test_tweetnacl_smoke $(BINDIR)/test_config $(BINDIR)/test_config_patch $(BINDIR)/test_memory $(BINDIR)/test_skill $(BINDIR)/test_provider $(BINDIR)/test_anthropic $(BINDIR)/test_openai $(BINDIR)/test_local_provider $(BINDIR)/test_router $(BINDIR)/test_heartbeat $(BINDIR)/test_crypto $(BINDIR)/test_hardware_stub $(BINDIR)/test_board_detect $(BINDIR)/test_hardware_libgpiod $(BINDIR)/test_hardware_i2c $(BINDIR)/test_hardware_camera $(BINDIR)/test_pin_tables $(BINDIR)/test_hardware_init $(BINDIR)/test_hardware_tools $(BINDIR)/test_registry $(BINDIR)/test_ws $(BINDIR)/test_agent $(BINDIR)/test_channel $(BINDIR)/test_cli $(BINDIR)/test_shell $(BINDIR)/test_file $(BINDIR)/test_telegram $(BINDIR)/test_discord_helpers $(BINDIR)/test_web_search $(BINDIR)/test_cron $(BINDIR)/test_context $(BINDIR)/test_manifest_build $(BINDIR)/test_manifest_keys $(BINDIR)/test_jcs $(BINDIR)/test_asap_envelope $(BINDIR)/test_asap_ulid $(BINDIR)/test_asap_client $(BINDIR)/test_asap_registry $(BINDIR)/test_asap_server $(BINDIR)/test_asap_invoke $(BINDIR)/test_asap_log $(BINDIR)/test_auth $(BINDIR)/test_gateway_http $(BINDIR)/test_gateway_db_lock $(BINDIR)/test_static $(BINDIR)/test_sandbox $(BINDIR)/test_allowlist $(BINDIR)/test_rate_limit
rm -f $(WS_TEST_O) $(SANDBOX_TEST_O) $(BINDIR)/asap_registry_test.o $(BINDIR)/asap_invoke_test.o $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(BINDIR)/shellclaw $(BINDIR)/test_tweetnacl_smoke $(BINDIR)/test_config $(BINDIR)/test_config_patch $(BINDIR)/test_memory $(BINDIR)/test_skill $(BINDIR)/test_provider $(BINDIR)/test_anthropic $(BINDIR)/test_openai $(BINDIR)/test_local_provider $(BINDIR)/test_router $(BINDIR)/test_heartbeat $(BINDIR)/test_crypto $(BINDIR)/test_hardware_stub $(BINDIR)/test_board_detect $(BINDIR)/test_hardware_libgpiod $(BINDIR)/test_hardware_i2c $(BINDIR)/test_hardware_camera $(BINDIR)/test_pin_tables $(BINDIR)/test_hardware_init $(BINDIR)/test_hardware_tools $(BINDIR)/test_registry $(BINDIR)/test_ws $(BINDIR)/test_agent $(BINDIR)/test_channel $(BINDIR)/test_cli $(BINDIR)/test_shell $(BINDIR)/test_file $(BINDIR)/test_telegram $(BINDIR)/test_discord_helpers $(BINDIR)/test_web_search $(BINDIR)/test_cron $(BINDIR)/test_context $(BINDIR)/test_manifest_build $(BINDIR)/test_manifest_keys $(BINDIR)/test_jcs $(BINDIR)/test_asap_envelope $(BINDIR)/test_asap_ulid $(BINDIR)/test_asap_client $(BINDIR)/test_asap_registry $(BINDIR)/test_asap_server $(BINDIR)/test_asap_invoke $(BINDIR)/test_asap_log $(BINDIR)/test_auth $(BINDIR)/test_gateway_http $(BINDIR)/test_gateway_db_lock $(BINDIR)/test_static $(BINDIR)/test_sandbox $(BINDIR)/test_allowlist $(BINDIR)/test_rate_limit
rm -rf $(BINDIR)/*.dSYM $(DSYMDIR)
rm -f $(BOOTSTRAP_DISPATCH_STUB_O) $(TOOL_RELOAD_STUB_O) $(RELOAD_CHANNEL_STUB_O) $(HTTP_RELOAD_STUB_O)
17 changes: 15 additions & 2 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,28 @@ The primary goals are: prevent sandboxed shell commands from escaping to host de

| Surface | Mechanism | Notes |
|---------|-----------|-------|
| Shell (sandbox on) | `fork()` + user ns when needed + `unshare(CLONE_NEWNS \| CLONE_NEWNET \| CLONE_NEWPID)` + fork for PID 1 + Landlock workspace bound + `prctl(PR_SET_NO_NEW_PRIVS)` | Fail-closed if namespaces or Landlock cannot apply. Isolation uses a control pipe, not `sh` exit 122/123. See [Linux sandbox (Jetson)](#linux-sandbox-jetson) |
| Shell (sandbox off) | Plain `fork()` + substring fallback blocklist | **Not** a security boundary; stderr warning |
| Shell (sandbox on) | `fork()` + user ns when needed + `unshare(CLONE_NEWNS \| CLONE_NEWNET \| CLONE_NEWPID)` + fork for PID 1 + Landlock workspace bound + `prctl(PR_SET_NO_NEW_PRIVS)` + secret-free environment | Fail-closed if namespaces or Landlock cannot apply. The command child keeps `PATH`, locale, `TERM`, `TZ`, and `HOME` set to the workspace. Host API keys and bot tokens are not inherited. Isolation uses a control pipe, not `sh` exit 122/123. See [Linux sandbox (Jetson)](#linux-sandbox-jetson) |
| Shell (sandbox off) | Plain `fork()` + substring fallback blocklist | **Not** a security boundary; the full host environment is inherited — `printenv` works there (see [Command environment model](#command-environment-model)); stderr warning |
| Allowlist | Substring blocklist + workspace containment (ancestor walk, quoted/embedded `/` `~`, `$HOME`/`$PWD`, `file:` URLs, relative names) | Defense-in-depth string scan; Landlock is the kernel host-FS bound |
| Landlock | Ruleset on configured `workspace_path` (RW workspace + traverse-only `/` + RO `/bin` `/usr` `/lib` and `/etc/ssl/certs` only; RW `/dev/null`, `/dev/zero`, `/dev/urandom`, `/dev/tty` without `IOCTL_DEV`). Child `fchdir`s the workspace before `restrict_self`. A failed `landlock_add_rule` fails the ruleset closed. | Primary host-FS gate; `/` is `READ_DIR` only so `/etc/passwd` stays closed. `/etc/ssl/private` is not granted. Blocks symlink and `chr(47)+` host reads |
| cgroups v2 | `memory.max`, `cpu.max`; isolator writes `cgroup.procs` before the command fork | Best-effort; non-fatal if cgroup write fails |
| Hardware GPIO/I2C | libgpiod / `i2c-dev` in agent process | Not exposed inside shell namespace |

Implementation: [`src/sandbox/sandbox.c`](../src/sandbox/sandbox.c), [`src/sandbox/allowlist.c`](../src/sandbox/allowlist.c).

### Command environment model

The two shell paths differ in environment exposure as much as in isolation, so the model is stated explicitly:

| Path | Environment the command sees | `printenv` inside the command |
|------|------------------------------|------------------------------|
| Sandboxed (`sandbox.enabled = true`) | Scrubbed whitelist only: `PATH`, `LANG`, `LC_ALL`, `LC_CTYPE`, `TERM`, and `TZ` keep their host values when set, and `HOME` is set to the configured workspace (when one is set). Everything else is dropped (`clearenv` before `exec`). | Only the whitelist: provider keys, bot tokens, and loader variables are gone. |
| Unsandboxed fallback (`sandbox.enabled = false`, the default when the key is omitted) | The full agent (host) environment is inherited. | Works — it prints the agent's environment, secrets included. Intentional: this path is reduced isolation, not a security boundary; a stderr warning is printed per command. |

Jetson-relevant variables that deliberately do **not** pass into the sandbox: `LD_LIBRARY_PATH` and all other `LD_*` (including `LD_PRELOAD`), `CUDA_HOME` and other `CUDA_*`, `TMPDIR`, `SSL_CERT_FILE`, and proxy variables (`http_proxy`, `https_proxy`, `no_proxy`, …). Sandboxed workloads that rely on them must set them per command (for example `LD_LIBRARY_PATH=… cmd`); there is no `[sandbox]` passthrough list in v1.0. Tool paths that genuinely need host setup (camera capture) run argv-only `execvp` in the agent process, outside the shell sandbox.

Implementation: [`src/sandbox/sandbox_env.c`](../src/sandbox/sandbox_env.c).

---

## Linux sandbox (Jetson)
Expand Down
95 changes: 79 additions & 16 deletions src/sandbox/sandbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
* procfs, applies Landlock, sets PR_SET_PDEATHSIG, and closes fds >= 3
* so a timeout SIGKILL of the isolator cannot leave the command under
* host init. The isolator joins its cgroup before the command fork so
* memory.max and cpu.max apply to sh -c. Limits degrade if unavailable.
* memory.max and cpu.max apply to sh -c. The command process then
* replaces the inherited environment so provider keys and bot tokens
* are not readable via printenv. Limits degrade if unavailable.
*
* Non-Linux path: plain fork() + execl(); a warning is emitted to stderr.
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L

#include "sandbox/sandbox.h"
#include "sandbox/sandbox_env.h"
#include "sandbox/sandbox_landlock.h"
#include <errno.h>
#include <fcntl.h>
Expand Down Expand Up @@ -107,36 +110,90 @@ static void cgroup_remove(const char *base, const char *name)

#endif /* __linux__ */

static size_t drain_pipe(int fd, char *buf, size_t cap, int timeout_ms)
/* Sandbox unit tests link drain_pipe as a global under
* SHELLCLAW_SANDBOX_TEST (CR-21 provider-test pattern). CI runners revoke
* user namespaces, so the sandbox_exec success-path tests skip there and
* the wall-clock deadline loop would otherwise stay uncovered. */
#ifdef SHELLCLAW_SANDBOX_TEST
#define SANDBOX_UNIT
#else
#define SANDBOX_UNIT static
#endif

/* Docs in sandbox.h. */
long monotonic_elapsed_ms(const struct timespec *start)
{
struct timespec now;
long ms;

if (clock_gettime(CLOCK_MONOTONIC, &now) != 0)
return -1;
ms = (now.tv_sec - start->tv_sec) * 1000L
+ (now.tv_nsec - start->tv_nsec) / 1000000L;
return ms < 0 ? 0 : ms;
}

int poll_remain_ms(int timeout_ms, long elapsed_ms, int slice_cap)
{
long remain;

remain = (long)timeout_ms - elapsed_ms;
if (remain > slice_cap)
return slice_cap;
return (int)remain;
}

/*
* Stop at timeout_ms of wall time, not idle poll time. A writer that keeps
* the pipe readable otherwise never advances the old counter, so the agent
* stays blocked until cap fills.
*/
SANDBOX_UNIT size_t drain_pipe(int fd, char *buf, size_t cap, int timeout_ms, int *hit_deadline)
{
struct timespec start;
size_t total = 0;
int elapsed_ms = 0;
char chunk[512];
if (cap == 0) return 0;
while (total < cap - 1 && elapsed_ms < timeout_ms) {

if (hit_deadline)
*hit_deadline = 0;
if (cap == 0)
return 0;
/* Unchecked on purpose: CLOCK_MONOTONIC failure is not a realistic case
* (vDSO). If it ever failed, monotonic_elapsed_ms keeps returning -1,
* the deadline never fires, and poll slices stay bounded. */
(void)clock_gettime(CLOCK_MONOTONIC, &start);
while (total < cap - 1) {
struct pollfd pfd;
long elapsed_ms;
int slice;
int r;
ssize_t n;

elapsed_ms = monotonic_elapsed_ms(&start);
if (elapsed_ms >= (long)timeout_ms) {
if (hit_deadline)
*hit_deadline = 1;
break;
}
slice = poll_remain_ms(timeout_ms, elapsed_ms, PIPE_POLL_SLICE_MS);
pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
slice = timeout_ms - elapsed_ms;
if (slice > PIPE_POLL_SLICE_MS) slice = PIPE_POLL_SLICE_MS;
r = poll(&pfd, 1, slice);
if (r < 0) {
if (errno == EINTR) continue;
if (errno == EINTR)
continue;
break;
}
if (r == 0) {
elapsed_ms += slice;
if (r == 0)
continue;
}
n = read(fd, chunk, sizeof(chunk));
if (n <= 0) break;
if (n <= 0)
break;
{
size_t add = (size_t)n;
if (total + add >= cap - 1) add = cap - 1 - total;
if (total + add >= cap - 1)
add = cap - 1 - total;
memcpy(buf + total, chunk, add);
total += add;
}
Expand Down Expand Up @@ -446,6 +503,7 @@ int sandbox_exec(const char *cmd, char *out, size_t out_cap,
#endif
close(errpipe[1]);
close_inherited_fds();
sandbox_scrub_command_environment(workspace);
execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
_exit(127);
}
Expand All @@ -467,9 +525,14 @@ int sandbox_exec(const char *cmd, char *out, size_t out_cap,
return report_isolation_failure(out, out_cap, iso);
}
}
total = drain_pipe(pipefd[0], out, out_cap, timeout_ms);
close(pipefd[0]);
timed_out = reap_child(pid, NULL);
{
int drain_deadline = 0;
total = drain_pipe(pipefd[0], out, out_cap, timeout_ms, &drain_deadline);
close(pipefd[0]);
timed_out = reap_child(pid, NULL);
if (drain_deadline)
timed_out = 1;
}
if (timed_out && total < out_cap - 40)
snprintf(out + total, out_cap - total, "\n[Sandbox: command timed out after %d ms]",
timeout_ms);
Expand Down
Loading
Loading