diff --git a/doc/api/ffi.md b/doc/api/ffi.md index 33eef15106124f..9839d115239d7e 100644 --- a/doc/api/ffi.md +++ b/doc/api/ffi.md @@ -533,6 +533,7 @@ native memory directly. The caller must guarantee that: * `length` stays within the allocated native region. * no native code frees or repurposes that memory while JavaScript still uses the `Buffer`. +* Memory protection is observed. If these guarantees are not met, reading or writing the `Buffer` can corrupt memory or crash the process. diff --git a/test/ffi/ffi-test-common.js b/test/ffi/ffi-test-common.js index 4398464ea75e47..2ec76f453b215f 100644 --- a/test/ffi/ffi-test-common.js +++ b/test/ffi/ffi-test-common.js @@ -77,6 +77,7 @@ const fixtureSymbols = { array_set_i32: { parameters: ['pointer', 'u64', 'i32'], result: 'void' }, array_get_f64: { parameters: ['pointer', 'u64'], result: 'f64' }, array_set_f64: { parameters: ['pointer', 'u64', 'f64'], result: 'void' }, + readonly_memory: { parameters: [], result: 'pointer' }, }; function cString(value) { diff --git a/test/ffi/fixture_library/ffi_test_library.c b/test/ffi/fixture_library/ffi_test_library.c index fed2e57eefdfcf..7ba93f4b774be6 100644 --- a/test/ffi/fixture_library/ffi_test_library.c +++ b/test/ffi/fixture_library/ffi_test_library.c @@ -2,6 +2,7 @@ #include #include #include +#include #ifdef _WIN32 #define FFI_EXPORT __declspec(dllexport) @@ -378,3 +379,10 @@ FFI_EXPORT void array_set_f64(double* arr, size_t index, double value) { arr[index] = value; } + +FFI_EXPORT void * readonly_memory() { + // TODO(bengl) Add a Windows version of this. + + void * p = mmap(0, 4096, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + return p; +} diff --git a/test/ffi/test-ffi-readonly-write.js b/test/ffi/test-ffi-readonly-write.js new file mode 100644 index 00000000000000..54c6727d796776 --- /dev/null +++ b/test/ffi/test-ffi-readonly-write.js @@ -0,0 +1,31 @@ +// Flags: --experimental-ffi +'use strict'; +const { skipIfFFIMissing, isWindows, skip } = require('../common'); +const assert = require('node:assert'); +const { spawnSync } = require('node:child_process'); +const { test } = require('node:test'); +const { fixtureSymbols, libraryPath } = require('./ffi-test-common'); + +skipIfFFIMissing(); +if (isWindows) { + skip('This test currently relies on POSIX APIs'); +} + +test('writing to readonly memory via buffer results in SIGBUS', () => { + const symbols = JSON.stringify(fixtureSymbols); + const { stdout, status, signal } = spawnSync(process.execPath, [ + '--experimental-ffi', + '-p', + ` + const ffi = require('node:ffi'); + const { functions } = ffi.dlopen('${libraryPath}', ${symbols}) + const p = functions.readonly_memory(); + const b = ffi.toBuffer(p, 4096, false); + b[0] = 42; + console.log('success'); + `, + ]); + assert.strictEqual(signal, 'SIGBUS'); + assert.strictEqual(status, null); + assert.strictEqual(stdout.length, 0); +});