Repository navigation
fix(vm): fail closed on malformed pull/send bytecode #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| package vm | ||
|
|
||
| var ErrSendWhileMarkOpen = errSendWhileMarkOpen | ||
| var ( | ||
| ErrSendWhileMarkOpen = errSendWhileMarkOpen | ||
| ErrSetAssetWithQueuedFunds = errSetAssetWithQueuedFunds | ||
| ErrEndWithQueuedFunds = errEndWithQueuedFunds | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,13 @@ var ( | |
| errSaveWhileMarkOpen = errors.New("save while a mark is open") | ||
| ) | ||
|
|
||
| // Not statically decidable: whether a send drains the queue depends on the | ||
| // balances. | ||
| var ( | ||
| errSetAssetWithQueuedFunds = errors.New("set_current_asset with funds still queued") | ||
| errEndWithQueuedFunds = errors.New("run ended with funds still queued") | ||
| ) | ||
|
|
||
| type Vm struct { | ||
| Program Program | ||
| runstate *funds.RunState | ||
|
|
@@ -156,9 +163,6 @@ func Exec[S Store]( | |
| switch Opcode(instr.Opcode) { | ||
| // --- Domain-specific ops | ||
| case Op_PullAccount: | ||
| // TODO crashes if this is the last instruction (the ext word is | ||
| // missing): instrs[pc] reads past the end. e.g. a program ending in a | ||
| // lone Op_PullAccount word. | ||
| instrExt := instrs[pc] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 [major] Reject aliased pull destinations at runtime Exec still passes the destination and cap/overdraft register pointers directly to the funds engine without comparing their indices. An unverified pull whose destination aliases a bound therefore retains the malformed-bytecode behavior this PR says Exec rejects: the destination write can change the bound and allow an excessive debit. The new verifier test does not protect callers executing unverified bytecode. Suggestion: Before invoking Pull or PullUncapped, reject a destination equal to either non-nil cap or overdraft register with InternalError, and add direct Exec regression tests for both aliases. |
||
| pc++ | ||
|
|
||
|
|
@@ -292,6 +296,10 @@ func Exec[S Store]( | |
| if runstate.HasOpenMark() { | ||
| return funds.ExecutionResult{}, InternalError{Err: errSetAssetWhileMarkOpen} | ||
| } | ||
| // funds queued under the previous asset would be posted under the new one | ||
| if runstate.HasQueuedFunds() { | ||
| return funds.ExecutionResult{}, InternalError{Err: errSetAssetWithQueuedFunds} | ||
| } | ||
| currentAsset = stringsRegs[instr.A] | ||
| runstate.SetCurrentAsset(currentAsset) | ||
|
|
||
|
|
@@ -426,8 +434,6 @@ func Exec[S Store]( | |
| portionsRegs[instr.A].Set(r) | ||
|
|
||
| case Op_MetaMonetary: | ||
| // TODO crashes if this is the last instruction (the ext word carrying the | ||
| // amount destination is missing), same as Op_PullAccount. | ||
| instrExt := instrs[pc] | ||
| pc++ | ||
|
|
||
|
|
@@ -635,6 +641,11 @@ func Exec[S Store]( | |
| }) | ||
| } | ||
|
|
||
| // a debit that never became a posting | ||
| if runstate.HasQueuedFunds() { | ||
| return funds.ExecutionResult{}, InternalError{Err: errEndWithQueuedFunds} | ||
| } | ||
|
|
||
| postings := runstate.GetPostings() | ||
| for _, p := range postings { | ||
| if !funds.ValidatePosting(p) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 [major] Guard extension-word reads before indexing instructions
An unverified program ending with Op_PullAccount still executes instrs[pc] after pc has advanced past the final word, causing an index-out-of-range panic rather than the promised InternalError. Op_MetaMonetary retains the same unchecked read at line 437. The change removes the TODOs without implementing the stated fail-closed behavior.
Suggestion: Check that an extension word exists before each two-word instruction reads it and return an InternalError for truncation. Add direct Exec tests for each truncated opcode.