Skip to content

Commit a1f5de0

Browse files
Lexterl33tmhoste51am1r021jochem-brouwer
authored
Fix the vulnerability found in issue #3861 (#3863)
* Fix PUSHN Opcode Non-Compliance Co-authored-by: Lexterl33t <[email protected]> * Test for PUSHn non compliance Co-authored-by: mhoste51 <[email protected]> * Remove console.log from test * Comment moved above the code * Comment moved above the code (updated) * Update the user balance to 0 * Refacto after the review Co-authored-by: mhoste51 <[email protected]> * evm: update push logic * evm: test all push opcodes * evm: add stack test for code with jumps, truncated push opcodes * evm: fix cache item for truncated pushs * evm/interpreter: use subarray instead of slice for push bytes cache --------- Co-authored-by: Mhoste <[email protected]> Co-authored-by: Scorbajio <[email protected]> Co-authored-by: Jochem Brouwer <[email protected]>
1 parent 1774df6 commit a1f5de0

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

packages/evm/src/interpreter.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
bytesToBigInt,
1010
bytesToHex,
1111
equalsBytes,
12+
setLengthRight,
1213
} from '@ethereumjs/util'
1314
import debugDefault from 'debug'
1415

@@ -521,10 +522,14 @@ export class Interpreter {
521522
// skip over PUSH0-32 since no jump destinations in the middle of a push block
522523
if (opcode <= 0x7f) {
523524
if (opcode >= 0x60) {
524-
const extraSteps = opcode - 0x5f
525-
const push = bytesToBigInt(code.slice(i + 1, i + opcode - 0x5e))
525+
const bytesToPush = opcode - 0x5f
526+
let pushBytes = code.subarray(i + 1, i + opcode - 0x5e)
527+
if (pushBytes.length < bytesToPush) {
528+
pushBytes = setLengthRight(pushBytes, bytesToPush)
529+
}
530+
const push = bytesToBigInt(pushBytes)
526531
pushes[i + 1] = push
527-
i += extraSteps
532+
i += bytesToPush
528533
} else if (opcode === 0x5b) {
529534
// Define a JUMPDEST as a 1 in the valid jumps array
530535
jumps[i] = 1

packages/evm/src/opcodes/functions.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
bytesToInt,
2525
concatBytes,
2626
setLengthLeft,
27+
setLengthRight,
2728
} from '@ethereumjs/util'
2829
import { keccak256 } from 'ethereum-cryptography/keccak.js'
2930

@@ -935,11 +936,16 @@ export const handlers: Map<number, OpHandler> = new Map([
935936
runState.stack.push(runState.cachedPushes[runState.programCounter])
936937
runState.programCounter += numToPush
937938
} else {
938-
const loaded = bytesToBigInt(
939-
runState.code.subarray(runState.programCounter, runState.programCounter + numToPush),
939+
let loadedBytes = runState.code.subarray(
940+
runState.programCounter,
941+
runState.programCounter + numToPush,
940942
)
943+
if (loadedBytes.length < numToPush) {
944+
loadedBytes = setLengthRight(loadedBytes, numToPush)
945+
}
946+
941947
runState.programCounter += numToPush
942-
runState.stack.push(loaded)
948+
runState.stack.push(bytesToBigInt(loadedBytes))
943949
}
944950
},
945951
],

packages/evm/test/stack.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { Account, Address, bigIntToBytes, hexToBytes, setLengthLeft } from '@ethereumjs/util'
1+
import {
2+
Account,
3+
Address,
4+
bigIntToBytes,
5+
bytesToBigInt,
6+
hexToBytes,
7+
setLengthLeft,
8+
setLengthRight,
9+
} from '@ethereumjs/util'
210
import { assert, describe, it } from 'vitest'
311

412
import { createEVM } from '../src/index.js'
@@ -148,4 +156,30 @@ describe('Stack', () => {
148156
const reportedStack = s.getStack()
149157
assert.deepEqual(reportedStack, [BigInt(4), BigInt(6)])
150158
})
159+
160+
it('stack should return the padded value', async () => {
161+
const evm = await createEVM()
162+
163+
for (let pushN = 0x60; pushN <= 0x7f; pushN++) {
164+
// PUSHx 01
165+
const code = `0x${pushN.toString(16)}01`
166+
// PUSH 0x03 JUMP JUMPDEST < PUSHx 01 >
167+
const codeWithJumps = `0x6003565B${pushN.toString(16)}01`
168+
169+
const expectedStack = new Stack(1024)
170+
expectedStack.push(bytesToBigInt(setLengthRight(new Uint8Array([0x01]), pushN - 0x5f)))
171+
172+
const resWithoutJumps = await evm.runCall({
173+
data: hexToBytes(code),
174+
})
175+
const executionStack = resWithoutJumps.execResult.runState?.stack
176+
assert.deepEqual(executionStack, expectedStack, 'code without jumps ok')
177+
178+
const resWithJumps = await evm.runCall({
179+
data: hexToBytes(codeWithJumps),
180+
})
181+
const executionStackWithJumps = resWithJumps.execResult.runState?.stack
182+
assert.deepEqual(executionStackWithJumps, expectedStack, 'code with jumps ok')
183+
}
184+
})
151185
})

0 commit comments

Comments
 (0)