From 52b15ac88fb9e50bd192cff4e492839c4f6c95b9 Mon Sep 17 00:00:00 2001 From: Aditya Reddy <82123567+adityareddy-dev@users.noreply.github.com> Date: Wed, 4 Nov 2026 02:40:23 -0600 Subject: [PATCH] perf_hooks: copy shared data before importing histograms Copy SharedArrayBuffer-backed input before parsing its CBOR data so concurrent worker writes cannot change the bytes during parsing. Fixes: https://github.com/nodejs/node/issues/67152 Signed-off-by: Aditya Reddy <82123567+adityareddy-dev@users.noreply.github.com> --- src/histogram.cc | 9 +++++ ...test-perf-hooks-histogram-import-shared.js | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test/parallel/test-perf-hooks-histogram-import-shared.js diff --git a/src/histogram.cc b/src/histogram.cc index 6efe2d6985cc..cf0258c2af9a 100644 --- a/src/histogram.cc +++ b/src/histogram.cc @@ -3211,6 +3211,15 @@ void HistogramImpl::DoImport(const FunctionCallbackInfo& args) { static_cast(backing->Data()) + input->ByteOffset(); size_t len = input->ByteLength(); + // Another thread can write to a SharedArrayBuffer while it is parsed, so + // parse a private copy instead. + std::unique_ptr data_copy; + if (input->Buffer()->IsSharedArrayBuffer() && len > 0) { + data_copy = std::make_unique_for_overwrite(len); + memcpy(data_copy.get(), data, len); + data = data_copy.get(); + } + auto histogram = Histogram::Import(data, len); if (!histogram) { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid histogram export data"); diff --git a/test/parallel/test-perf-hooks-histogram-import-shared.js b/test/parallel/test-perf-hooks-histogram-import-shared.js new file mode 100644 index 000000000000..e491ff9b9f95 --- /dev/null +++ b/test/parallel/test-perf-hooks-histogram-import-shared.js @@ -0,0 +1,33 @@ +'use strict'; + +require('../common'); +const assert = require('node:assert'); +const { createHistogram, importHistogram } = require('node:perf_hooks'); +const kInvalid = { code: 'ERR_INVALID_ARG_VALUE' }; + +{ + // Data in a SharedArrayBuffer, including a sliced view, imports the same way + // as data in an ArrayBuffer. + const h = createHistogram(); + h.record(1); + h.record(42); + h.record(1000); + const exported = h.export(); + + for (const offset of [0, 3]) { + const sab = new SharedArrayBuffer(exported.length + offset + 5); + new Uint8Array(sab).fill(0xff); + const view = new Uint8Array(sab, offset, exported.length); + view.set(exported); + const imported = importHistogram(view); + assert.deepStrictEqual(imported.export(), exported); + + // Later writes do not change the imported histogram. + view.fill(0); + assert.deepStrictEqual(imported.export(), exported); + assert.throws(() => importHistogram(view), kInvalid); + } + + assert.throws( + () => importHistogram(new Uint8Array(new SharedArrayBuffer(0))), kInvalid); +}