From c72b3f21766c0d5a0db841f07feb8343a1da1aa3 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 9 Feb 2022 15:30:26 +0300 Subject: [PATCH] vm/emit: check big integer size Fix a bug where big integer could be emitted as 0 without reporting an error. Also, add tests. Signed-off-by: Evgeniy Stratonikov --- pkg/vm/emit/emit.go | 5 +++ pkg/vm/emit/emit_test.go | 70 ++++++++++++++++++++++++++++++++++++++++ pkg/vm/stackitem/item.go | 33 ++++++++++++------- 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 6c0417066..bff6807fd 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -84,6 +84,11 @@ func bigInt(w *io.BinWriter, n *big.Int, trySmall bool) { return } + if err := stackitem.CheckIntegerSize(n); err != nil { + w.Err = err + return + } + buf := bigint.ToPreallocatedBytes(n, make([]byte, 0, 32)) if len(buf) == 0 { Opcodes(w, opcode.PUSH0) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index 9d8a20257..09eb3d4ae 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -86,6 +86,76 @@ func TestEmitInt(t *testing.T) { }) } +func TestEmitBigInt(t *testing.T) { + t.Run("biggest positive number", func(t *testing.T) { + buf := io.NewBufBinWriter() + bi := big.NewInt(1) + bi.Lsh(bi, 255) + bi.Sub(bi, big.NewInt(1)) + + // sanity check + require.NotPanics(t, func() { stackitem.NewBigInteger(bi) }) + + BigInt(buf.BinWriter, bi) + require.NoError(t, buf.Err) + + expected := make([]byte, 33) + expected[0] = byte(opcode.PUSHINT256) + for i := 1; i < 32; i++ { + expected[i] = 0xFF + } + expected[32] = 0x7F + require.Equal(t, expected, buf.Bytes()) + }) + t.Run("smallest negative number", func(t *testing.T) { + buf := io.NewBufBinWriter() + bi := big.NewInt(-1) + bi.Lsh(bi, 255) + + // sanity check + require.NotPanics(t, func() { stackitem.NewBigInteger(bi) }) + + BigInt(buf.BinWriter, bi) + require.NoError(t, buf.Err) + + expected := make([]byte, 33) + expected[0] = byte(opcode.PUSHINT256) + expected[32] = 0x80 + require.Equal(t, expected, buf.Bytes()) + }) + t.Run("biggest positive number plus 1", func(t *testing.T) { + buf := io.NewBufBinWriter() + bi := big.NewInt(1) + bi.Lsh(bi, 255) + + // sanity check + require.Panics(t, func() { stackitem.NewBigInteger(bi) }) + + BigInt(buf.BinWriter, bi) + require.Error(t, buf.Err) + + t.Run("do not clear previous error", func(t *testing.T) { + buf.Reset() + expected := errors.New("expected") + buf.Err = expected + BigInt(buf.BinWriter, bi) + require.Equal(t, expected, buf.Err) + }) + }) + t.Run("smallest negative number minus 1", func(t *testing.T) { + buf := io.NewBufBinWriter() + bi := big.NewInt(-1) + bi.Lsh(bi, 255) + bi.Sub(bi, big.NewInt(1)) + + // sanity check + require.Panics(t, func() { stackitem.NewBigInteger(bi) }) + + BigInt(buf.BinWriter, bi) + require.Error(t, buf.Err) + }) +} + func getSlice(n int) []byte { data := make([]byte, n) for i := range data { diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index 8fccd7ca2..10b921c5c 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -384,21 +384,32 @@ type BigInteger big.Int // NewBigInteger returns an new BigInteger object. func NewBigInteger(value *big.Int) *BigInteger { - // There are 2 cases, when `BitLen` differs from actual size: - // 1. Positive integer with highest bit on byte boundary = 1. - // 2. Negative integer with highest bit on byte boundary = 1 - // minus some value. (-0x80 -> 0x80, -0x7F -> 0x81, -0x81 -> 0x7FFF). - sz := value.BitLen() - if sz > MaxBigIntegerSizeBits { - panic(errTooBigInteger) - } else if sz == MaxBigIntegerSizeBits { - if value.Sign() == 1 || value.TrailingZeroBits() != MaxBigIntegerSizeBits-1 { - panic(errTooBigInteger) - } + if err := CheckIntegerSize(value); err != nil { + panic(err) } return (*BigInteger)(value) } +// CheckIntegerSize checks that value size doesn't exceed VM limit for Interer. +func CheckIntegerSize(value *big.Int) error { + // There are 2 cases, when `BitLen` differs from actual size: + // 1. Positive integer with the highest bit on byte boundary = 1. + // 2. Negative integer with the highest bit on byte boundary = 1 + // minus some value. (-0x80 -> 0x80, -0x7F -> 0x81, -0x81 -> 0x7FFF). + sz := value.BitLen() + // This check is not required, just an optimization for the common case. + if sz < MaxBigIntegerSizeBits { + return nil + } + if sz > MaxBigIntegerSizeBits { + return errTooBigInteger + } + if value.Sign() == 1 || value.TrailingZeroBits() != MaxBigIntegerSizeBits-1 { + return errTooBigInteger + } + return nil +} + // Big casts i to the big.Int type. func (i *BigInteger) Big() *big.Int { return (*big.Int)(i)