Skip to content

Tighten ffi pointer bounds, sparse archive extraction, and the Windows default trust store#31581

Open
Jarred-Sumner wants to merge 8 commits into
mainfrom
claude/security-round-9
Open

Tighten ffi pointer bounds, sparse archive extraction, and the Windows default trust store#31581
Jarred-Sumner wants to merge 8 commits into
mainfrom
claude/security-round-9

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Three small hardening fixes in bun:ffi, Bun.Archive, and the Windows TLS trust store. Continuation of the recent hardening series; each comes with a regression test.

  • bun:ffi: ptr() rejects byteOffset values that fall outside the source view (previously a negative or oversized offset produced a pointer outside the buffer).
  • Bun.Archive: on POSIX platforms, sparse tar entries are extracted preserving their holes instead of being materialized at the full declared size, so a small archive can no longer expand to many GB on disk via sparse maps. Windows extraction behavior is unchanged from before this PR; marking the destination file sparse on Windows is left as a follow-up. (The glob-filtered extraction path is unchanged for now.)
  • TLS (Windows): the bundled BoringSSL no longer consults the compiled-in Unix-style certificate paths (/etc/ssl/...) for the default CA store on Windows — those resolve to user-creatable locations like C:\etc\ssl. SSL_CERT_FILE / SSL_CERT_DIR are still honored when explicitly set, and non-Windows platforms are unchanged.

Note for reviewers: the Windows trust-path regression test is necessarily Windows-only and creates/removes <drive-root>\etc\ssl\cert.pem during the run (skipped if the file already exists); flag if that's undesirable on persistent runners and it can be reworked to a subprocess with a redirected drive.

cargo check passes on all 10 CI targets; the ffi, archive, and TLS CA suites pass locally with no new failures.

…tore on Windows

On Windows, the bundled BoringSSL's built-in default cert file/dir paths resolve against the drive root, which is not a meaningful location there. Only honor SSL_CERT_FILE/SSL_CERT_DIR when explicitly set; Linux/macOS behavior is unchanged.
@robobun

robobun commented May 29, 2026

Copy link
Copy Markdown
Collaborator
Updated 4:50 PM PT - May 29th, 2026

@Jarred-Sumner, your commit 0532a928b0b2a62ef9445e13b3c2df587e482962 passed in Build #59092! 🎉


🧪   To try this PR locally:

bunx bun-pr 31581

That installs a local version of the PR into your bun-31581 executable, so you can run:

bun-31581 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Make bun:ffi ptr function safer #9094 - PR tightens ptr() to reject byteOffset values outside the source buffer/view, which is exactly the safety improvement requested in this issue

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #9094

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 519351c8-c287-4049-99f2-950441cfb6d1

📥 Commits

Reviewing files that changed from the base of the PR and between 44589b9 and 0532a92.

📒 Files selected for processing (2)
  • src/runtime/ffi/FFIObject.rs
  • test/js/bun/ffi/ffi.test.js

Walkthrough

Windows OpenSSL CA-store loading now reads OpenSSL default cert-file/dir helpers; libarchive gains sparse-file awareness (FFI + extraction changes + regression test); FFI pointer offset validation tightened with corresponding tests.

Changes

Windows Certificate Store Initialization

Layer / File(s) Summary
OpenSSL store initialization on Windows
packages/bun-usockets/src/crypto/root_certs.cpp
us_get_default_ca_store() reads OpenSSL default cert file/dir values on Windows, calls X509_STORE_load_locations() when available, and clears OpenSSL errors after loading.
Certificate store path tests
test/js/node/tls/test-use-system-ca.test.ts
Adds tests for default certificate-store path behavior: helper and two tests that verify verification fails with unset env and succeeds when SSL_CERT_FILE points to a temp CA bundle.

Sparse File Support in Archive Extraction

Layer / File(s) Summary
Archive sparse count FFI and wrapper
src/libarchive/lib.rs
Adds archive_entry_sparse_count to the libarchive FFI and a lib::Entry::sparse_count() wrapper.
Sparse file handling in extract_to_dir
src/libarchive/lib.rs
Determine is_sparse from sparse_count(), skip Linux/Android large-file preallocation for sparse entries, and call ftruncate after writes when sparse (non-Windows guarded).
Sparse file regression test
test/js/bun/archive.test.ts
New test constructs an old-GNU wholly-sparse tar entry (64 MiB logical size, no data), extracts it, asserts logical size, and checks on-disk block usage remains low on non-Windows platforms.

FFI Pointer Boundary Validation

Layer / File(s) Summary
Pointer offset bounds checking
src/runtime/ffi/FFIObject.rs
When applying a positive byteOffset, compute address with saturating_add, capture the view base, and reject pointers outside [base, base + byte_len]. Also update negative-offset handling in get_ptr_slice.
FFI pointer bounds test
test/js/bun/ffi/ffi.test.js
Test asserts ptr(buf, byteOffset) throws "byteOffset out of bounds" for negative/oversized offsets and succeeds for offsets 0 and 16.
  • Possibly related PRs:

    • oven-sh/bun#31175: Related changes to extract_to_dir regular-file extraction logic.
  • Suggested reviewers:

    • RiskyMH
    • dylan-conway
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides detailed explanations of each change with context, but does not include a structured 'How did you verify your code works?' section as required by the template. Add a structured 'How did you verify your code works?' section that explicitly lists the testing approach, even if briefly referencing the regression tests mentioned in the narrative.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: three hardening fixes for ffi pointer bounds, sparse archive extraction, and Windows TLS trust store behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

Caution

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

⚠️ Outside diff range comments (1)
test/js/node/tls/test-use-system-ca.test.ts (1)

133-140: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add expect(stderr).toBe("") for consistency with the second test.

The second test (line 171) asserts stderr is empty, but this test doesn't. Since both use bunEnv (which sets BUN_DEBUG_QUIET_LOGS=1), stderr should be empty on success. Adding the assertion helps catch unexpected debug output or regressions.

Suggested change
       const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

       // The planted certificate must not be in the default trust store, so the
       // fetch has to fail certificate verification.
       expect(stdout).not.toContain("FETCH_OK");
       expect(stdout).toContain("FETCH_ERR");
       expect(stdout).toMatch(/SELF_SIGNED|CERT|UNABLE_TO_VERIFY/i);
+      expect(stderr).toBe("");
       expect(exitCode).toBe(0);

Based on learnings: "In oven-sh/bun Jest/Bun test files under test/js/ that spawn subprocesses using bunEnv from the harness module, it's safe and intentional to assert expect(stderr).toBe("") unconditionally."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/js/node/tls/test-use-system-ca.test.ts` around lines 133 - 140, The test
captures subprocess output into stdout, stderr, and exitCode via proc (const
[stdout, stderr, exitCode] = ...); add an assertion expect(stderr).toBe("")
after those captures (mirroring the second test) so unexpected debug/diagnostic
output is detected; this is safe because the test uses bunEnv/bun debug quiet
logs and keeps behavior consistent with the other test that already asserts
stderr is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@test/js/node/tls/test-use-system-ca.test.ts`:
- Around line 133-140: The test captures subprocess output into stdout, stderr,
and exitCode via proc (const [stdout, stderr, exitCode] = ...); add an assertion
expect(stderr).toBe("") after those captures (mirroring the second test) so
unexpected debug/diagnostic output is detected; this is safe because the test
uses bunEnv/bun debug quiet logs and keeps behavior consistent with the other
test that already asserts stderr is empty.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0b00a10-975c-4217-8d67-75d5e1bec66d

📥 Commits

Reviewing files that changed from the base of the PR and between f2ab681 and f6fb184.

📒 Files selected for processing (1)
  • test/js/node/tls/test-use-system-ca.test.ts

Comment thread src/runtime/ffi/FFIObject.rs
Comment thread src/libarchive/lib.rs Outdated
Comment thread src/runtime/ffi/FFIObject.rs

@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.

All three issues I flagged are addressed and the current diff looks correct, but since this touches the Windows default TLS trust store and untrusted-archive extraction I'd like a human to sign off — particularly on the C:\etc\ssl\cert.pem test approach the description already calls out.

Extended reasoning...

Overview

Three independent hardening fixes: (1) bun:ffi ptr()/get_ptr_slice() now reject out-of-range byteOffset values via saturating_add/saturating_sub + unsigned_abs() and a two-sided bounds check; (2) Bun.Archive extraction skips Linux fallocate for sparse entries and (on non-Windows only) ftruncates to the declared real size so holes stay holes; (3) on Windows, us_get_default_ca_store() no longer calls X509_STORE_set_default_paths() (which would consult the compiled-in /etc/ssl/... paths that resolve to user-writable drive-root locations) and instead only honors explicit SSL_CERT_FILE/SSL_CERT_DIR env vars. Each fix has a regression test.

Prior review status

I raised three inline issues on earlier revisions (i64::MIN negation overflow in ptr_(), the Windows ftruncate allocating the full declared size on NTFS, and the same negation overflow in get_ptr_slice()). All three are now fixed in the current diff and the author resolved each thread with a clear explanation. The bug-hunting pass on the latest revision found nothing new.

Security risks

This is the crux of why I'm not auto-approving. The root_certs.cpp change alters how the default CA trust store is assembled on Windows — a change in the wrong direction here could either silently trust attacker-planted roots or break TLS verification entirely. The change looks correct to me (it strictly removes an implicit, attacker-influenceable trust source while preserving the explicit env-var override and the bundled root set), but trust-store initialization is exactly the category where a second pair of human eyes is warranted. The archive change handles untrusted tar input and the ffi change touches raw-pointer arithmetic; both are hardening in the safe direction and look right.

Level of scrutiny

Medium-high. Individually each hunk is small and mechanical, but the surfaces are security-sensitive (crypto trust store, untrusted-archive extraction, FFI pointer bounds). The author also explicitly asked reviewers to weigh in on whether the Windows regression test — which creates and removes <drive-root>\etc\ssl\cert.pem on the runner — is acceptable on persistent CI machines or should be reworked. That's a judgment call I shouldn't make on the author's behalf.

Other factors

The one CI failure (streams-leak.test.ts on x64-baseline) is unrelated to anything this PR touches. Tests cover the new behavior on the platforms where it applies; Windows sparse-file handling is explicitly deferred to a follow-up and the code is gated accordingly.

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.

2 participants