fix(runtime): harden trap jumps and bound safe LEB reads#477
Conversation
Switch trap recovery to sigsetjmp/siglongjmp so signal masks are restored after CPU traps, and add explicit end-bounded decoding in readSafeLEBNumber with call-site updates across bytecode visitors and interpreter paths. Made-with: Cursor
⚡ Performance Regression Check Results✅ Performance Check Passed (interpreter)Performance Benchmark Results (threshold: 25%)
Summary: 194 benchmarks, 0 regressions ✅ Performance Check Passed (multipass)Performance Benchmark Results (threshold: 25%)
Summary: 194 benchmarks, 0 regressions |
There was a problem hiding this comment.
Pull request overview
This PR hardens runtime trap recovery for JIT execution and makes “safe” LEB decoding explicitly end-bounded to avoid reading past bytecode buffers.
Changes:
- Switch CPU-trap recovery in JIT paths from
setjmp/longjmptosigsetjmp/siglongjmp(restoring signal masks after traps). - Update trap-thread state helpers to store
sigjmp_bufand jump viasiglongjmp. - Change
readSafeLEBNumberto accept an explicitEndpointer and update bytecode visitor + interpreter call sites accordingly.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/utils/wasm.h |
Updates readSafeLEBNumber to be end-bounded and adds new error signaling for unexpected ends / overly long encodings. |
src/runtime/runtime.cpp |
Switches JIT trap wrappers to sigsetjmp (with signal-mask saving). |
src/common/traphandler.h |
Migrates WASM trap TLS from jmp_buf to sigjmp_buf and uses siglongjmp. |
src/common/evm_traphandler.h |
Migrates EVM trap TLS from jmp_buf to sigjmp_buf and uses siglongjmp. |
src/action/interpreter.cpp |
Updates interpreter immediate decoding call sites to pass an end pointer into readSafeLEBNumber. |
src/action/bytecode_visitor.h |
Updates bytecode visitor immediate decoding and load/store handlers to pass an end pointer into readSafeLEBNumber. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| while (Ip < End && Count < MaxBytes) { | ||
| Byte = *Ip++; | ||
| Result |= ((T)(Byte & 0x7f)) << Shift; | ||
| Shift += 7; | ||
| Count++; |
There was a problem hiding this comment.
readSafeLEBNumber() now bounds the read length, but it no longer validates that the decoded value actually fits in T (e.g., u32 values requiring 33+ bits). For unsigned immediates this can wrap/truncate (e.g., 2^32 decodes to 0), which can break correctness and potentially bypass downstream bounds checks (offsets/indices). Consider reusing the same LEBIntTooLarge / signed-top-bits validation logic from readLEBNumber() (and/or accumulating into std::make_unsigned_t and checking the final high bits) so out-of-range encodings are rejected instead of truncated.
| public: | ||
| CallThreadState(runtime::Instance *Inst, jmp_buf *Env, void *FrameAddr, | ||
| CallThreadState(runtime::Instance *Inst, sigjmp_buf *Env, void *FrameAddr, | ||
| void *PC = nullptr) | ||
| : Inst(Inst) { |
There was a problem hiding this comment.
This header now uses POSIX sigjmp_buf/siglongjmp, but it still only includes . is not required to declare sigjmp_buf/sigsetjmp/siglongjmp in standard C++; relying on it can break builds on some libcs/standard libraries. Prefer including <setjmp.h> (or otherwise ensuring the POSIX declarations are available) under ZEN_ENABLE_CPU_EXCEPTION.
| public: | ||
| EVMCallThreadState(runtime::EVMInstance *Inst, jmp_buf *Env, void *FrameAddr, | ||
| void *PC = nullptr) | ||
| EVMCallThreadState(runtime::EVMInstance *Inst, sigjmp_buf *Env, | ||
| void *FrameAddr, void *PC = nullptr) | ||
| : Inst(Inst) { |
There was a problem hiding this comment.
This header now uses POSIX sigjmp_buf/siglongjmp, but it still only includes . is not required to declare sigjmp_buf/sigsetjmp/siglongjmp in standard C++; relying on it can break builds on some libcs/standard libraries. Prefer including <setjmp.h> (or otherwise ensuring the POSIX declarations are available) under ZEN_ENABLE_CPU_EXCEPTION.
Switch trap recovery to sigsetjmp/siglongjmp so signal masks are restored after CPU traps, and add explicit end-bounded decoding in readSafeLEBNumber with call-site updates across bytecode visitors and interpreter paths.
Made-with: Cursor
1. Does this PR affect any open issues?(Y/N) and add issue references (e.g. "fix #123", "re #123".):
2. What is the scope of this PR (e.g. component or file name):
3. Provide a description of the PR(e.g. more details, effects, motivations or doc link):
4. Are there any breaking changes?(Y/N) and describe the breaking changes(e.g. more details, motivations or doc link):
5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links:
6. Release note