Skip to content

ffi: reject unsafe integers as length or offset - #66216

Merged
nodejs-github-bot merged 1 commit into
nodejs:mainfrom
christianaurichzm:ffi-size-upper-bound
Sep 30, 2026
Merged

nodejs-github-bot merged 1 commit into
nodejs:mainfrom
christianaurichzm:ffi-size-upper-bound

Conversation

@christianaurichzm

@christianaurichzm christianaurichzm commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

GetValidatedSize() checks lengths and offsets against static_cast<double>(SIZE_MAX), which rounds up to 2^64 on 64-bit platforms. So 2 ** 64 gets through, and the static_cast<size_t>() after it is undefined behavior. With GCC 13.3 on x64 it gives 0:

const ffi = require('node:ffi');

const buf = Buffer.from([7]);
const ptr = ffi.getRawPointer(buf);

ffi.setUint8(ptr, 2 ** 64, 42);
console.log(buf[0]); // 42
console.log(ffi.toBuffer(ptr, 2 ** 64).length); // 0

The limit is now Number.MAX_SAFE_INTEGER instead of just below 2 ** 64. Anything larger may already have been rounded before it gets here (2 ** 53 + 1 === 2 ** 53), so it can't be trusted as an offset. The export*() helpers already cap their length at that value, and so does setInt64() for number values. SIZE_MAX is still the limit on 32-bit.

This changes one error code: when buffer.constants.MAX_LENGTH is Number.MAX_SAFE_INTEGER (64-bit without the V8 sandbox), toBuffer() / toArrayBuffer() with MAX_LENGTH + 1 now throw ERR_OUT_OF_RANGE instead of ERR_BUFFER_TOO_LARGE. The existing assertions for that case now only run where MAX_LENGTH is smaller, and the new test covers the other case.

On main the new test segfaults (getUint8(ptr, 2 ** 53) reads 8 PiB past the pointer). With this change test/ffi passes, parallel and sequential show no new failures, and make lint is clean.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/ffi

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Sep 22, 2026
@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.28%. Comparing base (c0ed28f) to head (0558424).
⚠️ Report is 200 commits behind head on main.

Files with missing lines Patch % Lines
src/ffi/data.cc 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66216      +/-   ##
==========================================
- Coverage   90.29%   90.28%   -0.01%     
==========================================
  Files         790      789       -1     
  Lines      272883   272879       -4     
  Branches    52118    52112       -6     
==========================================
- Hits       246387   246370      -17     
- Misses      16943    16966      +23     
+ Partials     9553     9543      -10     
Files with missing lines Coverage Δ
src/ffi/data.cc 77.24% <50.00%> (-1.05%) ⬇️

... and 44 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Renegade334

Copy link
Copy Markdown
Member

My personal feeling is that we should be rejecting unsafe integers here entirely, there is no circumstance in which we should be allowing reads/writes from offsets that are being rounded.

I can't imagine a world in which someone is referencing a contiguous buffer that is more than 9 petabytes in size.

GetValidatedSize() checks the value against
static_cast<double>(SIZE_MAX), which rounds up to 2^64 on 64-bit
platforms. A length or offset of 2 ** 64 gets through, and the cast to
size_t after it is undefined behavior. With GCC on x64 it gives 0, so
ffi.setUint8(ptr, 2 ** 64, 42) writes to ptr instead of throwing.

Anything above Number.MAX_SAFE_INTEGER may already have been rounded
by the time it gets here, so reject those values too. The export*()
helpers already cap their length there, and so does setInt64() for
number values. SIZE_MAX is still the limit on 32-bit platforms.

When buffer.constants.MAX_LENGTH is Number.MAX_SAFE_INTEGER, as on
64-bit builds without the V8 sandbox, toBuffer() and toArrayBuffer()
now throw ERR_OUT_OF_RANGE for MAX_LENGTH + 1 instead of
ERR_BUFFER_TOO_LARGE.

Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
@christianaurichzm

Copy link
Copy Markdown
Contributor Author

Makes sense, agreed. It now rejects anything above Number.MAX_SAFE_INTEGER (SIZE_MAX still applies on 32-bit), which is where the export*() helpers already cap. One side effect: on 64-bit, toBuffer(ptr, MAX_LENGTH + 1) now throws ERR_OUT_OF_RANGE instead of ERR_BUFFER_TOO_LARGE, since that value is exactly 2 ** 53, so I scoped the old assertion to builds where MAX_LENGTH is smaller. PTAL

@christianaurichzm christianaurichzm changed the title ffi: reject 2**64 as a length or offset ffi: reject unsafe integers as length or offset Sep 23, 2026
@christianaurichzm

Copy link
Copy Markdown
Contributor Author

@Renegade334 this now rejects unsafe integers as you suggested. If this looks good to you, could you also add request-ci? Thanks!

@Renegade334 Renegade334 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@Renegade334 Renegade334 added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 29, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 29, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@panva panva added the resume-ci Add this label to resume the latest eligible Jenkins CI run on a PR with an approving review. label Sep 30, 2026
@github-actions github-actions Bot removed the resume-ci Add this label to resume the latest eligible Jenkins CI run on a PR with an approving review. label Sep 30, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@panva panva added author ready PRs with CI started, the required approvals, and no outstanding review comments. commit-queue PRs queued for automated landing through the Commit Queue. labels Sep 30, 2026
@nodejs-github-bot
nodejs-github-bot merged commit b9bb1bd into nodejs:main Sep 30, 2026
90 of 91 checks passed
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Landed in b9bb1bd

@nodejs-github-bot nodejs-github-bot removed the commit-queue PRs queued for automated landing through the Commit Queue. label Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs with CI started, the required approvals, and no outstanding review comments. c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants