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 <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2022-02-09 15:30:26 +03:00
parent 0394a79ef8
commit c72b3f2176
3 changed files with 97 additions and 11 deletions

View file

@ -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)

View file

@ -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 {

View file

@ -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)