Skip to content

sys: cache memfd_create ENOSYS + BUN_FEATURE_FLAG_DISABLE_MEMFD - #29465

Merged
Jarred-Sumner merged 3 commits into
mainfrom
claude/memfd-enosys-fallback
Apr 19, 2026
Merged

Jarred-Sumner merged 3 commits into
mainfrom
claude/memfd-enosys-fallback

Conversation

@dylan-conway

Copy link
Copy Markdown
Member

What

memfd_create requires kernel ≥ 3.17. A binary-level syscall audit (in #29461) found that every Bun caller already falls back on error — Blob → heap, spawn stdio → pipe, process IPC → socketpair — so kernel 3.10 (RHEL 7) works today, but every call retries the failing syscall.

This PR:

  • Caches ENOSYS in bun.sys.memfd_create so subsequent calls return immediately
  • Adds BUN_FEATURE_FLAG_DISABLE_MEMFD to force the fallback (seccomp environments, testing)
  • Tests that Blob and spawn-stdin work with the flag set
  • Fixes docs/installation.mdx: "minimum kernel 5.1" was never true (the io_uring check it referenced has zero callers). Actual floor is ~3.10 with degraded atomicity.

Complements #29461 (glibc 2.17).

Test plan

  • CI: linux test suite passes with new memfd-disabled.test.ts
  • CI: zig check on all platforms

memfd_create requires kernel ≥ 3.17. All existing callers (Blob, spawn
stdio, process IPC) already fall back to heap/pipe/socketpair on error,
so kernel 3.10 (RHEL 7) works today — but every call retries the
failing syscall.

- Cache ENOSYS in bun.sys.memfd_create so subsequent calls fail fast
- Add BUN_FEATURE_FLAG_DISABLE_MEMFD to force the fallback path
  (useful under seccomp, and for testing)
- Test the fallback paths with the flag set
- docs: replace incorrect "minimum kernel 5.1" (nothing requires 5.1;
  io_uring detection has zero callers) with the actual floor

Complements #29461 (glibc 2.17 floor).
@robobun

robobun commented Apr 18, 2026 •

Copy link
Copy Markdown
Collaborator

Instead of returning a synthetic ENOSYS from inside the wrapper, expose
canUseMemfd() (atomic tristate + env-flag check) and have the three
callers consult it before attempting the syscall — same shape as
canUseCopyFileRangeSyscall() and RWFFlagSupport.isMaybeSupported().
The wrapper still latches ENOSYS to -1 on first real failure.
@coderabbitai

coderabbitai Bot commented Apr 18, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Added graceful degradation for memfd usage: a feature-flag, a cached atomic latch for memfd unavailability, runtime guards that avoid memfd paths when unavailable, docs clarifying kernel support, and Linux-only tests exercising the fallback behavior.

Changes

Cohort / File(s) Summary
Documentation
docs/installation.mdx
Clarified Linux kernel guidance to state Bun still recommends kernel ≥ 5.6 but can run on older kernels (as far back as 3.10) via graceful degradation of newer syscalls.
Feature flag
src/env_var.zig
Added BUN_FEATURE_FLAG_DISABLE_MEMFD feature-flag definition (newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_MEMFD", .{})).
Sys / capability latch
src/sys.zig
Added module-scope atomic memfd_enosys and exported pub fn canUseMemfd() bool; memfd_create() now latches .NOSYS into memfd_enosys and canUseMemfd() factors in the feature-flag and platform.
Allocators
src/allocators/LinuxMemFdAllocator.zig
shouldUse(bytes) now early-returns false when bun.sys.canUseMemfd() is false, preventing memfd usage when unavailable.
Process / spawn
src/bun.js/api/bun/process.zig, src/bun.js/api/bun/spawn/stdio.zig
Linux memfd fast-paths for buffered stdio now require bun.sys.canUseMemfd(); Stdio.useMemfd checks canUseMemfd() before attempting memfd_create.
Tests
test/js/bun/memfd-disabled.test.ts
Added two Linux-only tests that spawn Bun with BUN_FEATURE_FLAG_DISABLE_MEMFD set to validate fallback behavior for piping a Blob into stdin and for large Response bodies.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main changes: caching memfd_create ENOSYS errors and adding a feature flag to disable memfd.
Description check ✅ Passed The description provides comprehensive context covering what changed, why (kernel compatibility), how it was verified, and includes a test plan, though test checkboxes are unchecked.

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


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/installation.mdx`:
- Line 24: Remove the stray non-printable control character immediately
following the bold token "**Linux users**" in the installation note; replace it
with a normal punctuation mark such as an em-dash (—) or colon (:) and ensure
there are no remaining invisible characters so the line reads e.g. "**Linux
users** — The `unzip` package..." in docs/installation.mdx.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 37bce8f2-9485-41a1-94c1-fc610192f49c

📥 Commits

Reviewing files that changed from the base of the PR and between aa16dd5 and 1b85a6c.

📒 Files selected for processing (4)
  • docs/installation.mdx
  • src/env_var.zig
  • src/sys.zig
  • test/js/bun/memfd-disabled.test.ts

Comment thread docs/installation.mdx
</CodeGroup>
<Note>
**Linux users**  The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is strongly recommended, but the minimum is 5.1. Use `uname -r` to check Kernel version.
**Linux users**  The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove the accidental control character in the Linux note.

There’s a non-printable character after **Linux users** that should be replaced with normal punctuation (e.g., — or :) to avoid rendering/copy issues.

✏️ Proposed fix
-      **Linux users** � The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.
+      **Linux users** — The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Linux users** The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.
**Linux users** — The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/installation.mdx` at line 24, Remove the stray non-printable control
character immediately following the bold token "**Linux users**" in the
installation note; replace it with a normal punctuation mark such as an em-dash
(—) or colon (:) and ensure there are no remaining invisible characters so the
line reads e.g. "**Linux users** — The `unzip` package..." in
docs/installation.mdx.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sys.zig (1)

3134-3156: ⚠️ Potential issue | 🟠 Major

Short-circuit memfd_create() once ENOSYS is latched.

Right now the latch is only written. Repeated direct calls to bun.sys.memfd_create() will still perform the failing syscall instead of returning the cached fallback signal immediately.

🔧 Proposed fix
 pub fn memfd_create(name: [:0]const u8, flags_: MemfdFlags) Maybe(bun.FD) {
     if (comptime !Environment.isLinux) `@compileError`("linux only!");
+    if (memfd_enosys.load(.monotonic)) {
+        return .{ .err = Error.fromCode(.NOSYS, .memfd_create) };
+    }
     var flags: u32 = `@intFromEnum`(flags_);
     while (true) {
         const rc = std.os.linux.memfd_create(name, flags);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sys.zig` around lines 3134 - 3156, The memfd_create function currently
only sets the memfd_enosys latch on .NOSYS but continues to attempt the syscall
on subsequent calls; add an early check at the start of pub fn
memfd_create(name, flags_: MemfdFlags) to short‑circuit when memfd_enosys is set
(use memfd_enosys.load(.monotonic)) and immediately return a Maybe(bun.FD) error
representing ENOSYS instead of invoking std.os.linux.memfd_create; place this
check before the while loop and use the same error representation path as
Maybe(bun.FD).errnoSys would produce for .NOSYS so callers get the cached
fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/sys.zig`:
- Around line 3134-3156: The memfd_create function currently only sets the
memfd_enosys latch on .NOSYS but continues to attempt the syscall on subsequent
calls; add an early check at the start of pub fn memfd_create(name, flags_:
MemfdFlags) to short‑circuit when memfd_enosys is set (use
memfd_enosys.load(.monotonic)) and immediately return a Maybe(bun.FD) error
representing ENOSYS instead of invoking std.os.linux.memfd_create; place this
check before the while loop and use the same error representation path as
Maybe(bun.FD).errnoSys would produce for .NOSYS so callers get the cached
fallback behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc77957b-1a90-44a0-94b6-534a0a65b2a0

📥 Commits

Reviewing files that changed from the base of the PR and between 1b85a6c and e9e4d6e.

📒 Files selected for processing (4)
  • src/allocators/LinuxMemFdAllocator.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/sys.zig

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — straightforward ENOSYS caching with a follow-on feature flag, matching established patterns.

Extended reasoning...

Overview

This PR touches 4 files: a docs fix, a new feature flag entry in env_var.zig, the core caching logic in sys.zig, and a new test file. The scope is narrow and self-contained.

Security risks

None. The change only adds a short-circuit in memfd_create that returns ENOSYS earlier (when already known unsupported or explicitly disabled). No auth, crypto, or permission-sensitive code is involved.

Level of scrutiny

Low-to-medium. The logic in sys.zig is a small write-once atomic flag (false → true, never reversed), so monotonic ordering is sound — the worst-case race is two threads each attempt the syscall once before the cached value propagates. The feature flag follows the identical pattern of other BUN_FEATURE_FLAG_DISABLE_* flags. The doc correction is accurate per the PR description (io_uring check had zero callers).

Other factors

No bugs were found by the automated system. All callers already fall back on error (Blob → heap, spawn stdio → pipe, IPC → socketpair), so this change purely optimizes the retry path without altering behavior on kernels that support memfd_create. Tests cover both the Blob and spawn-stdin paths under the disabled flag.

@Jarred-Sumner
Jarred-Sumner merged commit 91d066b into main Apr 19, 2026
60 of 65 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/memfd-enosys-fallback branch April 19, 2026 06:01
dylan-conway added a commit that referenced this pull request Apr 20, 2026
## Summary
- The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e`
script via `JSON.stringify`, yielding a 131,394-byte argv entry — over
Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn`
failed with `E2BIG`. Now the payload is generated inside the child;
`canUseMemfd` has no size gate for in-memory Blobs so the same code path
is exercised.
- Both tests asserted `stderr === ""`, which fails on ASAN debug builds
because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`.
Added a `stripAsanWarning` filter (same approach as
`broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`,
etc).

Follow-up to #29465.

## Test plan
- [x] Reproduced original failure on Linux: `E2BIG: argument list too
long, posix_spawn`
- [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail)
- [ ] CI: linux test suite green

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
robobun pushed a commit that referenced this pull request Apr 20, 2026
## Summary
- The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e`
script via `JSON.stringify`, yielding a 131,394-byte argv entry — over
Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn`
failed with `E2BIG`. Now the payload is generated inside the child;
`canUseMemfd` has no size gate for in-memory Blobs so the same code path
is exercised.
- Both tests asserted `stderr === ""`, which fails on ASAN debug builds
because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`.
Added a `stripAsanWarning` filter (same approach as
`broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`,
etc).

Follow-up to #29465.

## Test plan
- [x] Reproduced original failure on Linux: `E2BIG: argument list too
long, posix_spawn`
- [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail)
- [ ] CI: linux test suite green

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
structwafel pushed a commit to structwafel/bun that referenced this pull request Apr 25, 2026
…-sh#29465)

## What

`memfd_create` requires kernel ≥ 3.17. A binary-level syscall audit (in
oven-sh#29461) found that every Bun caller already falls back on error — Blob →
heap, spawn stdio → pipe, process IPC → socketpair — so kernel 3.10
(RHEL 7) works today, but every call retries the failing syscall.

This PR:
- Caches ENOSYS in `bun.sys.memfd_create` so subsequent calls return
immediately
- Adds `BUN_FEATURE_FLAG_DISABLE_MEMFD` to force the fallback (seccomp
environments, testing)
- Tests that Blob and spawn-stdin work with the flag set
- Fixes `docs/installation.mdx`: "minimum kernel 5.1" was never true
(the io_uring check it referenced has zero callers). Actual floor is
~3.10 with degraded atomicity.

Complements oven-sh#29461 (glibc 2.17).

## Test plan

- [ ] CI: linux test suite passes with new `memfd-disabled.test.ts`
- [ ] CI: zig check on all platforms
structwafel pushed a commit to structwafel/bun that referenced this pull request Apr 25, 2026
## Summary
- The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e`
script via `JSON.stringify`, yielding a 131,394-byte argv entry — over
Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn`
failed with `E2BIG`. Now the payload is generated inside the child;
`canUseMemfd` has no size gate for in-memory Blobs so the same code path
is exercised.
- Both tests asserted `stderr === ""`, which fails on ASAN debug builds
because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`.
Added a `stripAsanWarning` filter (same approach as
`broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`,
etc).

Follow-up to oven-sh#29465.

## Test plan
- [x] Reproduced original failure on Linux: `E2BIG: argument list too
long, posix_spawn`
- [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail)
- [ ] CI: linux test suite green

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
…-sh#29465)

## What

`memfd_create` requires kernel ≥ 3.17. A binary-level syscall audit (in
oven-sh#29461) found that every Bun caller already falls back on error — Blob →
heap, spawn stdio → pipe, process IPC → socketpair — so kernel 3.10
(RHEL 7) works today, but every call retries the failing syscall.

This PR:
- Caches ENOSYS in `bun.sys.memfd_create` so subsequent calls return
immediately
- Adds `BUN_FEATURE_FLAG_DISABLE_MEMFD` to force the fallback (seccomp
environments, testing)
- Tests that Blob and spawn-stdin work with the flag set
- Fixes `docs/installation.mdx`: "minimum kernel 5.1" was never true
(the io_uring check it referenced has zero callers). Actual floor is
~3.10 with degraded atomicity.

Complements oven-sh#29461 (glibc 2.17).

## Test plan

- [ ] CI: linux test suite passes with new `memfd-disabled.test.ts`
- [ ] CI: zig check on all platforms
xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
## Summary
- The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e`
script via `JSON.stringify`, yielding a 131,394-byte argv entry — over
Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn`
failed with `E2BIG`. Now the payload is generated inside the child;
`canUseMemfd` has no size gate for in-memory Blobs so the same code path
is exercised.
- Both tests asserted `stderr === ""`, which fails on ASAN debug builds
because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`.
Added a `stripAsanWarning` filter (same approach as
`broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`,
etc).

Follow-up to oven-sh#29465.

## Test plan
- [x] Reproduced original failure on Linux: `E2BIG: argument list too
long, posix_spawn`
- [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail)
- [ ] CI: linux test suite green

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.

3 participants