mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
blockchain: Implement stricter bounds checking.
This implements stricter bounds checking during transaction spend journal decoding.
This commit is contained in:
parent
77a14a9ead
commit
b9b863f5a7
@ -130,27 +130,48 @@ func deserializeToMinimalOutputs(serialized []byte) ([]*stake.MinimalOutput, int
|
||||
}
|
||||
|
||||
// readDeserializeSizeOfMinimalOutputs reads the size of the stored set of
|
||||
// minimal outputs without allocating memory for the structs themselves. It
|
||||
// will panic if the function reads outside of memory bounds.
|
||||
func readDeserializeSizeOfMinimalOutputs(serialized []byte) int {
|
||||
// minimal outputs without allocating memory for the structs themselves.
|
||||
func readDeserializeSizeOfMinimalOutputs(serialized []byte) (int, error) {
|
||||
numOutputs, offset := deserializeVLQ(serialized)
|
||||
if offset == 0 {
|
||||
return offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (num outputs)")
|
||||
}
|
||||
for i := 0; i < int(numOutputs); i++ {
|
||||
// Amount
|
||||
_, bytesRead := deserializeVLQ(serialized[offset:])
|
||||
if bytesRead == 0 {
|
||||
return offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (output amount)")
|
||||
}
|
||||
offset += bytesRead
|
||||
|
||||
// Script version
|
||||
_, bytesRead = deserializeVLQ(serialized[offset:])
|
||||
if bytesRead == 0 {
|
||||
return offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (output script version)")
|
||||
}
|
||||
offset += bytesRead
|
||||
|
||||
// Script
|
||||
var scriptSize uint64
|
||||
scriptSize, bytesRead = deserializeVLQ(serialized[offset:])
|
||||
if bytesRead == 0 {
|
||||
return offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (output script size)")
|
||||
}
|
||||
offset += bytesRead
|
||||
|
||||
if uint64(len(serialized[offset:])) < scriptSize {
|
||||
return offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (output script)")
|
||||
}
|
||||
|
||||
offset += int(scriptSize)
|
||||
}
|
||||
|
||||
return offset
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// ConvertUtxosToMinimalOutputs converts the contents of a UTX to a series of
|
||||
@ -565,27 +586,11 @@ func putSpentTxOut(target []byte, stxo *spentTxOut) int {
|
||||
// An error will be returned if the version is not serialized as a part of the
|
||||
// stxo and is also not provided to the function.
|
||||
func decodeSpentTxOut(serialized []byte, stxo *spentTxOut, amount int64, height uint32, index uint32) (int, error) {
|
||||
// Ensure there are bytes to decode.
|
||||
if len(serialized) == 0 {
|
||||
return 0, errDeserialize("no serialized bytes")
|
||||
}
|
||||
|
||||
// Deserialize the header code.
|
||||
// Deserialize the flags.
|
||||
flags, offset := deserializeVLQ(serialized)
|
||||
if offset >= len(serialized) {
|
||||
return offset, errDeserialize("unexpected end of data after " +
|
||||
"spent tx out flags")
|
||||
}
|
||||
|
||||
// Decode the flags. If the flags are non-zero, it means that the
|
||||
// transaction was fully spent at this spend.
|
||||
if decodeFlagsFullySpent(byte(flags)) {
|
||||
isCoinBase, hasExpiry, txType, _ := decodeFlags(byte(flags))
|
||||
|
||||
stxo.isCoinBase = isCoinBase
|
||||
stxo.hasExpiry = hasExpiry
|
||||
stxo.txType = txType
|
||||
stxo.txFullySpent = true
|
||||
if offset == 0 {
|
||||
return 0, errDeserialize("unexpected end of data during " +
|
||||
"decoding (flags)")
|
||||
}
|
||||
|
||||
// Decode the compressed txout. We pass false for the amount flag,
|
||||
@ -609,22 +614,28 @@ func decodeSpentTxOut(serialized []byte, stxo *spentTxOut, amount int64, height
|
||||
// Deserialize the containing transaction if the flags indicate that
|
||||
// the transaction has been fully spent.
|
||||
if decodeFlagsFullySpent(byte(flags)) {
|
||||
isCoinBase, hasExpiry, txType, _ := decodeFlags(byte(flags))
|
||||
|
||||
stxo.isCoinBase = isCoinBase
|
||||
stxo.hasExpiry = hasExpiry
|
||||
stxo.txType = txType
|
||||
stxo.txFullySpent = true
|
||||
|
||||
txVersion, bytesRead := deserializeVLQ(serialized[offset:])
|
||||
offset += bytesRead
|
||||
if offset == 0 || offset > len(serialized) {
|
||||
return offset, errDeserialize("unexpected end of data " +
|
||||
"after version")
|
||||
if bytesRead == 0 {
|
||||
return offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (tx version)")
|
||||
}
|
||||
offset += bytesRead
|
||||
|
||||
stxo.txVersion = uint16(txVersion)
|
||||
|
||||
if stxo.txType == stake.TxTypeSStx {
|
||||
sz := readDeserializeSizeOfMinimalOutputs(serialized[offset:])
|
||||
if sz == 0 || sz > len(serialized[offset:]) {
|
||||
return offset, errDeserialize("corrupt data for ticket " +
|
||||
"fully spent stxo stakeextra")
|
||||
sz, err := readDeserializeSizeOfMinimalOutputs(serialized[offset:])
|
||||
if err != nil {
|
||||
return offset + sz, errDeserialize(fmt.Sprintf("unable to decode "+
|
||||
"ticket outputs: %v", err))
|
||||
}
|
||||
|
||||
stakeExtra := make([]byte, sz)
|
||||
copy(stakeExtra, serialized[offset:offset+sz])
|
||||
stxo.stakeExtra = stakeExtra
|
||||
|
||||
@ -493,53 +493,81 @@ func TestStxoDecodeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
stxo spentTxOut
|
||||
txVersion int32 // When the txout is not fully spent.
|
||||
serialized []byte
|
||||
bytesRead int // Expected number of bytes read.
|
||||
errType error
|
||||
bytesRead int // Expected number of bytes read.
|
||||
}{
|
||||
{
|
||||
name: "nothing serialized",
|
||||
// [EOF]
|
||||
name: "nothing serialized (no flags)",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes(""),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 0,
|
||||
},
|
||||
{
|
||||
name: "no data after flags w/o version",
|
||||
// [<flags 00> EOF]
|
||||
name: "no compressed txout script version",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("00"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 1,
|
||||
},
|
||||
{
|
||||
name: "no data after flags code",
|
||||
// [<flags 10> <script version 00> EOF]
|
||||
name: "no tx version data after empty script for a fully spent regular stxo",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("14"),
|
||||
serialized: hexToBytes("1000"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 1,
|
||||
bytesRead: 2,
|
||||
},
|
||||
{
|
||||
name: "no tx version data after script",
|
||||
// [<flags 10> <script version 00> <compressed pk script 01 6e ...> EOF]
|
||||
name: "no tx version data after a pay-to-script-hash script for a fully spent regular stxo",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
serialized: hexToBytes("1000016edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 23,
|
||||
},
|
||||
{
|
||||
name: "no stakeextra data after script for ticket",
|
||||
// [<flags 14> <script version 00> <compressed pk script 01 6e ...> <tx version 01> EOF]
|
||||
name: "no stakeextra data after script for a fully spent ticket stxo",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e8601"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 24,
|
||||
},
|
||||
{
|
||||
name: "incomplete compressed txout",
|
||||
// [<flags 14> <script version 00> <compressed pk script 01 6e ...> <tx version 01> <stakeextra {num outputs 01}> EOF]
|
||||
name: "truncated stakeextra data after script for a fully spent ticket stxo (num outputs only)",
|
||||
stxo: spentTxOut{},
|
||||
txVersion: 1,
|
||||
serialized: hexToBytes("1432"),
|
||||
serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e860101"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 2,
|
||||
bytesRead: 25,
|
||||
},
|
||||
{
|
||||
// [<flags 14> <script version 00> <compressed pk script 01 6e ...> <tx version 01> <stakeextra {num outputs 01} {amount 0f}> EOF]
|
||||
name: "truncated stakeextra data after script for a fully spent ticket stxo (num outputs and amount only)",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e8601010f"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 26,
|
||||
},
|
||||
{
|
||||
// [<flags 14> <script version 00> <compressed pk script 01 6e ...> <tx version 01> <stakeextra {num outputs 01} {amount 0f} {script version 00}> EOF]
|
||||
name: "truncated stakeextra data after script for a fully spent ticket stxo (num outputs, amount, and script version only)",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e8601010f00"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 27,
|
||||
},
|
||||
{
|
||||
// [<flags 14> <script version 00> <compressed pk script 01 6e ...> <tx version 01> <stakeextra {num outputs 01} {amount 0f} {script version 00} {script size 1a} {25 bytes of script instead of 26}> EOF]
|
||||
name: "truncated stakeextra data after script for a fully spent ticket stxo (script size specified as 0x1a, but only 0x19 bytes provided)",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e8601010f001aba76a9140cdf9941c0c221243cb8672cd1ad2c4c0933850588"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 28,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -653,9 +653,9 @@ func decodeCompressedTxOut(serialized []byte, compressionVersion uint32,
|
||||
// remaining for the compressed script.
|
||||
var compressedAmount uint64
|
||||
compressedAmount, bytesRead = deserializeVLQ(serialized)
|
||||
if bytesRead >= len(serialized) {
|
||||
if bytesRead == 0 {
|
||||
return 0, 0, nil, bytesRead, errDeserialize("unexpected end of " +
|
||||
"data after compressed amount")
|
||||
"data during decoding (compressed amount)")
|
||||
}
|
||||
amount = int64(decompressTxOutAmount(compressedAmount))
|
||||
offset += bytesRead
|
||||
@ -664,12 +664,17 @@ func decodeCompressedTxOut(serialized []byte, compressionVersion uint32,
|
||||
// Decode the script version.
|
||||
var scriptVersion uint64
|
||||
scriptVersion, bytesRead = deserializeVLQ(serialized[offset:])
|
||||
if bytesRead == 0 {
|
||||
return 0, 0, nil, offset, errDeserialize("unexpected end of " +
|
||||
"data during decoding (script version)")
|
||||
}
|
||||
offset += bytesRead
|
||||
|
||||
// Decode the compressed script size and ensure there are enough bytes
|
||||
// left in the slice for it.
|
||||
scriptSize := decodeCompressedScriptSize(serialized[offset:],
|
||||
compressionVersion)
|
||||
// Note: scriptSize == 0 is OK (an empty compressed script is valid)
|
||||
if scriptSize < 0 {
|
||||
return 0, 0, nil, offset, errDeserialize("negative script size")
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user