Skip to content

src: fix crash when reading length on Storage.prototype#63529

Open
3zrv wants to merge 1 commit into
nodejs:mainfrom
3zrv:fix-webstorage-length
Open

src: fix crash when reading length on Storage.prototype#63529
3zrv wants to merge 1 commit into
nodejs:mainfrom
3zrv:fix-webstorage-length

Conversation

@3zrv
Copy link
Copy Markdown

@3zrv 3zrv commented May 24, 2026

Fixes #63514

The following one-liner reliably segfaults:

$ node -e 'globalThis.sessionStorage.setItem.length;globalThis.Storage.prototype.length'
[1]    42883 segmentation fault (core dumped)

Stack trace:

#0  sqlite3LockAndPrepare ()
#1  sqlite3_prepare_v2 ()
#2  node::webstorage::Storage::Length ()
#3  node::webstorage::StorageLengthGetter ()

Root cause

Storage's length accessor is defined on the prototype template:

Local<FunctionTemplate> length_getter =
    FunctionTemplate::New(isolate, StorageLengthGetter);   // no signature
ctor_tmpl->PrototypeTemplate()->SetAccessorProperty(env->length_string(), ...);

Fix

Create the getter's FunctionTemplate with a v8::Signature bound to the Storage constructor template, exactly as the methods do. V8 now rejects an incompatible receiver with a TypeError: Illegal invocation before the C++ callback runs matching browser behavior instead of crashing. Real localStorage/sessionStorage instances are unaffected.

@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 May 24, 2026
Comment on lines +29 to +42
test('reading "length" on Storage.prototype throws instead of crashing', async () => {
// Refs: the `length` getter is defined on Storage.prototype but used to lack
// a signature, so accessing it on the prototype (not a real Storage instance)
// unwrapped a non-Storage object and segfaulted.
const cp = await spawnPromisified(process.execPath, [
'-e',
'globalThis.sessionStorage.setItem.length;globalThis.Storage.prototype.length',
]);

assert.strictEqual(cp.signal, null);
assert.strictEqual(cp.code, 1);
assert.strictEqual(cp.stdout, '');
assert.match(cp.stderr, /Illegal invocation/);
});
Copy link
Copy Markdown
Contributor

@aduh95 aduh95 May 25, 2026

Choose a reason for hiding this comment

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

Suggested change
test('reading "length" on Storage.prototype throws instead of crashing', async () => {
// Refs: the `length` getter is defined on Storage.prototype but used to lack
// a signature, so accessing it on the prototype (not a real Storage instance)
// unwrapped a non-Storage object and segfaulted.
const cp = await spawnPromisified(process.execPath, [
'-e',
'globalThis.sessionStorage.setItem.length;globalThis.Storage.prototype.length',
]);
assert.strictEqual(cp.signal, null);
assert.strictEqual(cp.code, 1);
assert.strictEqual(cp.stdout, '');
assert.match(cp.stderr, /Illegal invocation/);
});
test('calling "length" getter on invalid this throws', async () => {
assert.throws(() => Storage.prototype.length, TypeError);
const { get } = Object.getOwnPropertyDescriptor(Storage.prototype, 'length');
for (const thisArg of [null, undefined, 1n, -0, NaN, true, false, '', [], {}, Symbol()]) {
assert.throws(() => get.call(thisArg), TypeError);
}
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

Accessing Storage.prototype.length crashes the process

4 participants