Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/api/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ 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. For example, read-only memory pages must not
be written to.

If these guarantees are not met, reading or writing the `Buffer` can corrupt
memory or crash the process.
Expand Down
4 changes: 4 additions & 0 deletions test/ffi/ffi-test-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ const fixtureSymbols = {
array_set_f64: { parameters: ['pointer', 'u64', 'f64'], result: 'void' },
};

if (!common.isWindows) {
fixtureSymbols.readonly_memory = { parameters: [], result: 'pointer' };
}

function cString(value) {
return Buffer.from(`${value}\0`);
}
Expand Down
11 changes: 10 additions & 1 deletion test/ffi/fixture_library/ffi_test_library.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#define FFI_EXPORT __declspec(dllexport)
#else
#include <sys/mman.h>
#define FFI_EXPORT
#endif

Expand Down Expand Up @@ -378,3 +378,12 @@ FFI_EXPORT void array_set_f64(double* arr, size_t index, double value) {

arr[index] = value;
}

#ifndef _WIN32
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;
}
#endif
31 changes: 31 additions & 0 deletions test/ffi/test-ffi-readonly-write.js
Original file line number Diff line number Diff line change
@@ -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 fails', () => {
const symbols = JSON.stringify(fixtureSymbols);
const libPath = JSON.stringify(libraryPath);
const { stdout, status } = spawnSync(process.execPath, [
'--experimental-ffi',
'-p',
`
const ffi = require('node:ffi');
const { functions } = ffi.dlopen(${libPath}, ${symbols})
const p = functions.readonly_memory();
const b = ffi.toBuffer(p, 4096, false);
b[0] = 42;
console.log('success');
`,
]);
assert.notStrictEqual(status, 0);
assert.strictEqual(stdout.length, 0);
});
Loading