Conversation
Deserialized function bytecode was trusted to match its declared stack_size. A malformed stream could declare fewer operand stack slots than the bytecode uses, making JS_CallInternal() read/write past its stack-allocated frame (dynamic-stack-buffer-overflow, issue bellard#551). Reuse the compiler's bytecode graph exploration (compute_stack_size) at read time: it computes the maximum operand stack depth actually needed and additionally rejects invalid opcodes, out-of-bounds jump targets and stack underflows. Functions whose bytecode needs more slots than declared are refused with a SyntaxError. compute_stack_size() now takes (bc_buf, bc_len) instead of a JSFunctionDef so the reader can share it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
Fixes #551 —
JS_ReadObject()trusted thestack_sizefield of deserializedfunction bytecode. A malformed stream could declare fewer operand stack slots
than the bytecode actually uses;
JS_CallInternal()then allocates the framewith
alloca()sized by the declared counts and reads/writes past it(ASan
dynamic-stack-buffer-overflow, CWE-125/CWE-787).Root cause
JS_CallInternal()lays out the interpreter frame asarg_buf | var_buf | stack_buf | var_refs, sized entirely from serializedheader fields (
arg_count,var_count,stack_size,var_ref_count).The deserializer never verified that the bytecode stays inside the declared
operand-stack region, so undersized
stack_size(or plain stack underflowin the bytecode) slid
spout of the allocation.Fix
After
JS_ReadFunctionBytecode()completes inJS_ReadFunctionTag(), thefunction's bytecode is now run through the compiler's existing stack-depth
analysis (
compute_stack_size), refactored to take(bc_buf, bc_len)instead of a
JSFunctionDefso the reader can share it. The check rejectswith
SyntaxErrorwhen:b->stack_sizedeclares (the demonstrated undersized-frame case), or
invalid opcodes, out-of-bounds jump targets, inconsistent stack depth,
stack underflow (which is what the issue's 18-byte PoC actually does:
it pops from an empty operand stack at pc=0).
One fix point covers every path that produces executable function
bytecode: top-level functions, modules (
JS_ReadModulereads itsfunc_objthrough the same tag) and cpool-nested functions.Test
tests/bjson.cnow accepts a 5threadargument enablingJS_READ_OBJ_BYTECODE, andtest_bjson.jsgainsbjson_test_undersized_stack_frame()feeding the exact 17-byte PoC streamfrom the issue — asserted to be rejected with
SyntaxError.Verification
04be246): PoC driver dies with ASandynamic-stack-buffer-overflowREAD inJS_CallInternal(
quickjs.c:18267), matching the issue trace.JS_ReadObjectthrowsSyntaxError, clean exit, no sanitizerfindings.
JS_EVAL_FLAG_COMPILE_ONLY,serialized via
JS_WriteObject(JS_WRITE_OBJ_BYTECODE), read back andexecuted via
JS_EvalFunctionreturns the correct result — legitimatebytecode is not rejected.
make testsuites pass (test_bjsonincl. the new regression test,test_builtin --std,test_language,test_bigint,test_cyclic_import,test_closure,test_loop).Scope note
This validates the operand-stack dimension of the frame. Operand indices
into
var_buf/arg_buf/var_refs(e.g.get_locbeyondvar_count)are a distinct hardening surface, intentionally left out to keep the diff
reviewable.