Skip to content

security: enforce process limit to prevent fork bombs in sandbox#871

Closed
kagura-agent wants to merge 2 commits intoNVIDIA:mainfrom
kagura-agent:fix/sandbox-process-limit
Closed

security: enforce process limit to prevent fork bombs in sandbox#871
kagura-agent wants to merge 2 commits intoNVIDIA:mainfrom
kagura-agent:fix/sandbox-process-limit

Conversation

@kagura-agent
Copy link
Contributor

@kagura-agent kagura-agent commented Mar 25, 2026

Summary

Fixes #809 — sandbox user has ulimit -u unlimited, allowing fork bombs to exhaust host PID space.

Changes

Defense in depth across three paths:

Layer What Why
Dockerfile nproc hard limit (512) via /etc/security/limits.d/ Caps the sandbox user regardless of how the process starts
nemoclaw-start.sh (non-root) Explicit ulimit -u 512 limits.conf only applies with PAM (login/su/sudo), not direct exec
nemoclaw-start.sh (root, NEMOCLAW_CMD) Wrap gosu sandbox with ulimit Ad-hoc commands also inherit the cap

The gateway user is intentionally left uncapped — it manages multiple child processes and should not be restricted.

Why 512?

Generous for normal agent workloads (node, python, shell pipelines) while blocking exponential fork spawning. For reference, a typical OpenClaw agent session uses ~20-50 processes.

Testing

# Before: unlimited
$ ulimit -u
unlimited

# After: capped
$ ulimit -u
512

# Fork bomb blocked:
$ :(){ :|:& };:
bash: fork: retry: Resource temporarily unavailable

Summary by CodeRabbit

  • Chores
    • Container image now enforces a hard per-user process limit for the sandboxed runtime to reduce risk of resource exhaustion.
    • Startup process applies the same per-user process limit at launch (best-effort); if the limit cannot be set, a warning is emitted and startup continues.

…DIA#809)

The sandbox user currently has ulimit -u unlimited, leaving no protection
against fork bombs or process exhaustion attacks. A prompt-injected agent
could DoS the host by spawning processes until the system runs out of PIDs.

Defense in depth across three paths:

1. Dockerfile: set nproc hard limit (512) via /etc/security/limits.d/ so
   the sandbox user is capped regardless of how the process is started.

2. nemoclaw-start.sh (non-root): explicit ulimit -u 512 since limits.conf
   only applies when PAM is involved (login/su/sudo), not direct exec.

3. nemoclaw-start.sh (root, NEMOCLAW_CMD): wrap gosu sandbox with ulimit
   so ad-hoc commands also inherit the process cap.

The gateway user is intentionally left uncapped — it needs to manage
multiple child processes and should not be restricted.

512 processes is generous for normal agent workloads (node, python, shell
pipelines) while preventing exponential fork spawning.

Fixes NVIDIA#809
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR adds a hard nproc limit for the sandbox user at the OS level and applies a best-effort ulimit -u 512 in the startup script for both non-root and root execution paths to mitigate unbounded process creation.

Changes

Cohort / File(s) Summary
OS-level limits
Dockerfile
Appends creation of /etc/security/limits.d/sandbox-nproc.conf containing sandbox hard nproc 512 to set an OS hard process limit for the sandbox user.
Startup/runtime enforcement
scripts/nemoclaw-start.sh
Before launching the sandbox command, runs ulimit -u 512 (best-effort, warns on failure) in both non-root path and in the root-mode gosu sandbox wrapper so the process limit is applied at runtime.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I bound my hops to five-twelve bright,
No wild forks racing through the night,
A gentle limit, snug and tight,
The sandbox hums — all calm, polite. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: adding process limits to prevent fork bombs in the sandbox environment.
Linked Issues check ✅ Passed All coding requirements from issue #809 are met: process limits enforced via Dockerfile nproc config and ulimit commands in the startup script.
Out of Scope Changes check ✅ Passed All changes directly address the linked issue #809 security objective; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/nemoclaw-start.sh (1)

230-233: Consider adding a warning on ulimit failure for consistency.

In the non-root path (line 192), a warning is printed if ulimit fails. Here, the failure is silently ignored. While root should always succeed at setting ulimits, adding a warning would provide consistent observability across both execution paths.

♻️ Optional: Add warning on failure
 if [ ${`#NEMOCLAW_CMD`[@]} -gt 0 ]; then
-  exec gosu sandbox bash -c 'ulimit -u 512 2>/dev/null; exec "$@"' _ "${NEMOCLAW_CMD[@]}"
+  exec gosu sandbox bash -c 'ulimit -u 512 2>/dev/null || echo "[SECURITY WARNING] Could not set process limit (ulimit -u)" >&2; exec "$@"' _ "${NEMOCLAW_CMD[@]}"
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/nemoclaw-start.sh` around lines 230 - 233, The exec line that runs
commands as the sandbox user currently silences ulimit failures (the gosu bash
-c 'ulimit -u 512 2>/dev/null; exec "$@"' _ "${NEMOCLAW_CMD[@]}")—add the same
warning behavior used in the non-root path by checking ulimit's exit status and
printing a warning if it fails; modify the bash -c script to run ulimit -u 512,
capture its return code, and echo a warning to stderr (or use the existing
warning function) when that return code is non-zero before continuing to exec
"$@" so failures are observable while preserving the exec flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/nemoclaw-start.sh`:
- Around line 230-233: The exec line that runs commands as the sandbox user
currently silences ulimit failures (the gosu bash -c 'ulimit -u 512 2>/dev/null;
exec "$@"' _ "${NEMOCLAW_CMD[@]}")—add the same warning behavior used in the
non-root path by checking ulimit's exit status and printing a warning if it
fails; modify the bash -c script to run ulimit -u 512, capture its return code,
and echo a warning to stderr (or use the existing warning function) when that
return code is non-zero before continuing to exec "$@" so failures are
observable while preserving the exec flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8e98884-c4c6-4b93-bc38-b94be3a18037

📥 Commits

Reviewing files that changed from the base of the PR and between cec1e42 and 2739833.

📒 Files selected for processing (2)
  • Dockerfile
  • scripts/nemoclaw-start.sh

Address CodeRabbit nitpick: the non-root path already warns on
ulimit failure, but the root/gosu path silently ignored it.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/nemoclaw-start.sh (1)

187-193: Consider centralizing the nproc policy to avoid drift.

The same limit and warning logic appears twice; a shared helper + single constant would keep behavior consistent and easier to maintain.

♻️ Suggested refactor
@@
 OPENCLAW="$(command -v openclaw)" # Resolve once, use absolute path everywhere
+NPROC_LIMIT=512
+
+apply_nproc_limit() {
+  ulimit -u "${NPROC_LIMIT}" 2>/dev/null || \
+    echo "[SECURITY WARNING] Could not set process limit (ulimit -u)" >&2
+}
@@
-  ulimit -u 512 2>/dev/null || echo "[SECURITY WARNING] Could not set process limit (ulimit -u)"
+  apply_nproc_limit
@@
-  exec gosu sandbox bash -c 'ulimit -u 512 2>/dev/null || echo "[SECURITY WARNING] Could not set process limit (ulimit -u)" >&2; exec "$@"' _ "${NEMOCLAW_CMD[@]}"
+  exec gosu sandbox bash -c "$(declare -f apply_nproc_limit); apply_nproc_limit; exec \"\$@\"" _ "${NEMOCLAW_CMD[@]}"

Also applies to: 230-233

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/nemoclaw-start.sh` around lines 187 - 193, Extract the duplicated
ulimit logic into a single constant and helper in the script: define a
MAX_USER_PROCESSES variable (e.g. MAX_USER_PROCESSES=512) and a function (e.g.
set_nproc_limit()) that runs ulimit -u "$MAX_USER_PROCESSES" 2>/dev/null || echo
"[SECURITY WARNING] Could not set process limit (ulimit -u)"; then replace both
inline occurrences of the ulimit command with a call to set_nproc_limit to
centralize the policy and avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/nemoclaw-start.sh`:
- Around line 187-193: Extract the duplicated ulimit logic into a single
constant and helper in the script: define a MAX_USER_PROCESSES variable (e.g.
MAX_USER_PROCESSES=512) and a function (e.g. set_nproc_limit()) that runs ulimit
-u "$MAX_USER_PROCESSES" 2>/dev/null || echo "[SECURITY WARNING] Could not set
process limit (ulimit -u)"; then replace both inline occurrences of the ulimit
command with a call to set_nproc_limit to centralize the policy and avoid drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa01b982-96c3-404c-bbb7-21656f654354

📥 Commits

Reviewing files that changed from the base of the PR and between 2739833 and 552f415.

📒 Files selected for processing (1)
  • scripts/nemoclaw-start.sh

@kagura-agent
Copy link
Contributor Author

Closing — #830 (merged) covers process limits with a more comprehensive approach (also removes gcc/netcat and drops capabilities). Thanks for the review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SECURITY] No process limit (ulimit -u unlimited) — fork bomb unprotected

1 participant