Skip to content
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

Problem: x/tx not up to date to the released version #279

Merged
merged 2 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions x/tx/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,63 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

## v0.13.1

### Features

* [#19618](https://github.com/cosmos/cosmos-sdk/pull/19618) Add enum as string option to encoder.

### Improvements

* [#18857](https://github.com/cosmos/cosmos-sdk/pull/18857) Moved `FormatCoins` from `core/coins` to this package under `signing/textual`.

### Bug Fixes

* [#19265](https://github.com/cosmos/cosmos-sdk/pull/19265) Reject denoms that contain a comma.

## v0.13.0

### Improvements

* [#18740](https://github.com/cosmos/cosmos-sdk/pull/18740) Support nested messages when fetching signers up to a default depth of 32.

## v0.12.0

### Improvements

* [#18309](https://github.com/cosmos/cosmos-sdk/pull/18309) Update encoder so that amino types default to msg type url.

## v0.11.0

### Improvements

* [#17787](https://github.com/cosmos/cosmos-sdk/pull/17787) Drop tip support.

## v0.10.0

### Features

* [#17681](https://github.com/cosmos/cosmos-sdk/pull/17681) Add encoder `DefineTypeEncoding` method for defining custom type encodings.
* [#17600](https://github.com/cosmos/cosmos-sdk/pull/17600) Add encoder `DefineScalarEncoding` method for defining custom scalar encodings.
* [#17600](https://github.com/cosmos/cosmos-sdk/pull/17600) Add indent option to encoder.

## v0.9.1

### Improvements

* [#16936](https://github.com/cosmos/cosmos-sdk/pull/16936) Remove extra whitespace when marshalling module accounts.

## v0.9.0

### Bug Fixes

* [#16681](https://github.com/cosmos/cosmos-sdk/pull/16681): Catch and fix `(*Decoder).Decode` crash from invalid length prefix in Tx bytes.

### Improvements

* [#16846](https://github.com/cosmos/cosmos-sdk/pull/16846): Harmonize interface `signing.TypeResolver` with the rest of the codebase (orm and client/v2).
* [#16684](https://github.com/cosmos/cosmos-sdk/pull/16684): Use `io.WriteString`+`fmt.Fprintf` to remove unnecessary `string`->`[]byte` roundtrip.

## v0.8.0

### Improvements
Expand Down
8 changes: 8 additions & 0 deletions x/tx/decode/adr027.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ func rejectNonADR027TxRaw(txBytes []byte) error {
if m < 0 {
return fmt.Errorf("invalid length; %w", protowire.ParseError(m))
}

// Paranoia from possible varint decoding which can trivially
// be wrong due to the precarious nature of the format being tricked:
// https://cyber.orijtech.com/advisory/varint-decode-limitless
if m > len(txBytes) {
return fmt.Errorf("invalid length from decoding (%d) > len(txBytes) (%d)", m, len(txBytes))
}

// TxRaw only has bytes fields.
if wireType != protowire.BytesType {
return fmt.Errorf("expected %d wire type, got %d", protowire.BytesType, wireType)
Expand Down
32 changes: 20 additions & 12 deletions x/tx/decode/decode.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package decode

import (
"fmt"
"errors"

"github.com/cosmos/cosmos-proto/anyutil"
"google.golang.org/protobuf/proto"

v1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/errors"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/tx/signing"
)

Expand All @@ -33,7 +33,7 @@ type Options struct {
// NewDecoder creates a new Decoder for decoding transactions.
func NewDecoder(options Options) (*Decoder, error) {
if options.SigningContext == nil {
return nil, fmt.Errorf("signing context is required")
return nil, errors.New("signing context is required")
}

return &Decoder{
Expand All @@ -46,7 +46,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// Make sure txBytes follow ADR-027.
err := rejectNonADR027TxRaw(txBytes)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

var raw v1beta1.TxRaw
Expand All @@ -55,7 +55,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
fileResolver := d.signingCtx.FileResolver()
err = RejectUnknownFieldsStrict(txBytes, raw.ProtoReflect().Descriptor(), fileResolver)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

err = proto.Unmarshal(txBytes, &raw)
Expand All @@ -68,25 +68,25 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// allow non-critical unknown fields in TxBody
txBodyHasUnknownNonCriticals, err := RejectUnknownFields(raw.BodyBytes, body.ProtoReflect().Descriptor(), true, fileResolver)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

err = proto.Unmarshal(raw.BodyBytes, &body)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

var authInfo v1beta1.AuthInfo

// reject all unknown proto fields in AuthInfo
err = RejectUnknownFieldsStrict(raw.AuthInfoBytes, authInfo.ProtoReflect().Descriptor(), fileResolver)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

err = proto.Unmarshal(raw.AuthInfoBytes, &authInfo)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

theTx := &v1beta1.Tx{
Expand All @@ -97,17 +97,25 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {

var signers [][]byte
var msgs []proto.Message
seenSigners := map[string]struct{}{}
for _, anyMsg := range body.Messages {
msg, signerErr := anyutil.Unpack(anyMsg, fileResolver, d.signingCtx.TypeResolver())
if signerErr != nil {
return nil, errors.Wrap(ErrTxDecode, signerErr.Error())
return nil, errorsmod.Wrap(ErrTxDecode, signerErr.Error())
}
msgs = append(msgs, msg)
ss, signerErr := d.signingCtx.GetSigners(msg)
if signerErr != nil {
return nil, errors.Wrap(ErrTxDecode, signerErr.Error())
return nil, errorsmod.Wrap(ErrTxDecode, signerErr.Error())
}
for _, s := range ss {
_, seen := seenSigners[string(s)]
if seen {
continue
}
signers = append(signers, s)
seenSigners[string(s)] = struct{}{}
}
signers = append(signers, ss...)
}

return &DecodedTx{
Expand Down
33 changes: 29 additions & 4 deletions x/tx/decode/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package decode_test
import (
"encoding/hex"
"fmt"
"strings"
"testing"

"github.com/cosmos/cosmos-proto/anyutil"
Expand Down Expand Up @@ -85,10 +86,6 @@ func TestDecode(t *testing.T) {
Payer: "payer",
Granter: "",
},
Tip: &txv1beta1.Tip{ //nolint:staticcheck // we still need this deprecated struct
Amount: []*basev1beta1.Coin{{Amount: "100", Denom: "denom"}},
Tipper: "tipper",
},
},
Signatures: nil,
}
Expand Down Expand Up @@ -118,3 +115,31 @@ func (d dummyAddressCodec) StringToBytes(text string) ([]byte, error) {
func (d dummyAddressCodec) BytesToString(bz []byte) (string, error) {
return hex.EncodeToString(bz), nil
}

func TestDecodeTxBodyPanic(t *testing.T) {
crashVector := []byte{
0x0a, 0x0a, 0x09, 0xe7, 0xbf, 0xba, 0xe6, 0x82, 0x9a, 0xe6, 0xaa, 0x30,
}

cdc := new(dummyAddressCodec)
signingCtx, err := signing.NewContext(signing.Options{
AddressCodec: cdc,
ValidatorAddressCodec: cdc,
})
if err != nil {
t.Fatal(err)
}
dec, err := decode.NewDecoder(decode.Options{
SigningContext: signingCtx,
})
if err != nil {
t.Fatal(err)
}
_, err = dec.Decode(crashVector)
if err == nil {
t.Fatal("expected a non-nil error")
}
if g, w := err.Error(), "could not consume length prefix"; !strings.Contains(g, w) {
t.Fatalf("error mismatch\n%s\nodes not contain\n\t%q", g, w)
}
}
140 changes: 140 additions & 0 deletions x/tx/decode/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package decode

import (
"encoding/hex"
"testing"

"github.com/cosmos/cosmos-proto/anyutil"
fuzz "github.com/google/gofuzz"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"

bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/api/cosmos/crypto/secp256k1"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing"
)

var (
accSeq = uint64(2)

signerInfo = []*txv1beta1.SignerInfo{
{
PublicKey: pkAny,
ModeInfo: &txv1beta1.ModeInfo{
Sum: &txv1beta1.ModeInfo_Single_{
Single: &txv1beta1.ModeInfo_Single{
Mode: signingv1beta1.SignMode_SIGN_MODE_DIRECT,
},
},
},
Sequence: accSeq,
},
}

anyMsg, _ = anyutil.New(&bankv1beta1.MsgSend{})

pkAny, _ = anyutil.New(&secp256k1.PubKey{Key: []byte("foo")})
)

func generateAndAddSeedsFromTx(f *testing.F) {
f.Helper()
// 1. Add some seeds.
tx := &txv1beta1.Tx{
Body: &txv1beta1.TxBody{
Messages: []*anypb.Any{anyMsg},
Memo: "memo",
TimeoutHeight: 0,
},
AuthInfo: &txv1beta1.AuthInfo{
SignerInfos: signerInfo,
Fee: &txv1beta1.Fee{
Amount: []*basev1beta1.Coin{{Amount: "100", Denom: "denom"}},
GasLimit: 100,
Payer: "payer",
Granter: "",
},
},
Signatures: nil,
}
f.Add(mustMarshal(f, tx))
fz := fuzz.New()
// 1.1. Mutate tx as much and add those as seeds.
for i := 0; i < 1e4; i++ {
func() {
defer func() {
_ = recover() // Catch any panics and continue
}()
fz.Fuzz(tx)
f.Add(mustMarshal(f, tx))
}()
}
}

func FuzzInternal_rejectNonADR027TxRaw(f *testing.F) {
if testing.Short() {
f.Skip("Skipping in -short mode")
}

// 1. Add some seeds.
generateAndAddSeedsFromTx(f)

// 2. Now run the fuzzer.
f.Fuzz(func(t *testing.T, in []byte) {
// Just ensure it doesn't crash.
_ = rejectNonADR027TxRaw(in)
})
}

func FuzzDecode(f *testing.F) {
if testing.Short() {
f.Skip("Skipping in -short mode")
}

// 1. Add some seeds.
generateAndAddSeedsFromTx(f)

// 2. Now fuzz it.
cdc := new(asHexCodec)
signingCtx, err := signing.NewContext(signing.Options{
AddressCodec: cdc,
ValidatorAddressCodec: cdc,
})
if err != nil {
return
}
dec, err := NewDecoder(Options{
SigningContext: signingCtx,
})
if err != nil {
return
}

f.Fuzz(func(t *testing.T, in []byte) {
txr, err := dec.Decode(in)
if err == nil && txr == nil {
t.Fatal("inconsistency: err==nil yet tx==nil")
}
})
}

func mustMarshal(f *testing.F, m proto.Message) []byte {
f.Helper()
blob, err := proto.Marshal(m)
if err != nil {
f.Fatal(err)
}
return blob
}

type asHexCodec int

func (d asHexCodec) StringToBytes(text string) ([]byte, error) {
return hex.DecodeString(text)
}

func (d asHexCodec) BytesToString(bz []byte) (string, error) {
return hex.EncodeToString(bz), nil
}
9 changes: 9 additions & 0 deletions x/tx/decode/unknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUn

// consume length prefix of nested message
_, o := protowire.ConsumeVarint(fieldBytes)
if o < 0 {
err = fmt.Errorf("could not consume length prefix fieldBytes for nested message: %v: %w",
fieldMessage, protowire.ParseError(o))
return hasUnknownNonCriticals, err
} else if o > len(fieldBytes) {
err = fmt.Errorf("length prefix > len(fieldBytes) for nested message: %v", fieldMessage)
return hasUnknownNonCriticals, err
}

fieldBytes = fieldBytes[o:]

var err error
Expand Down
Loading
Loading