Skip to content

Commit

Permalink
fix(lib/runtime): prevents polkadot zero-address bug using `sr25519_v…
Browse files Browse the repository at this point in the history
…erify` version 1 (#3494)
  • Loading branch information
EclesioMeloJunior authored Oct 2, 2023
1 parent 8aed52e commit 8b93d5e
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 36 deletions.
54 changes: 34 additions & 20 deletions lib/babe/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ var (
errInvalidMandatoryDispatch = errors.New("invalid mandatory dispatch")
errLookupFailed = errors.New("lookup failed")
errValidatorNotFound = errors.New("validator not found")
errBadSigner = errors.New("invalid signing address")
)

func newUnknownError(data scale.VaryingDataTypeValue) error {
Expand Down Expand Up @@ -271,55 +272,68 @@ func (MandatoryDispatch) Index() uint { return 9 }

func (MandatoryDispatch) String() string { return "mandatory dispatch" }

func determineErrType(vdt scale.VaryingDataType) error {
// BadSigner A transaction with a mandatory dispatch
type BadSigner struct{}

// Index returns VDT index
func (BadSigner) Index() uint { return 10 }

func (BadSigner) String() string { return "invalid signing address" }

func determineErrType(vdt scale.VaryingDataType) (err error) {
vdtVal, err := vdt.Value()
if err != nil {
return fmt.Errorf("getting vdt value: %w", err)
}

switch val := vdtVal.(type) {
case Other:
return &DispatchOutcomeError{fmt.Sprintf("unknown error: %s", val)}
err = &DispatchOutcomeError{fmt.Sprintf("unknown error: %s", val)}
case CannotLookup:
return &DispatchOutcomeError{"failed lookup"}
err = &DispatchOutcomeError{"failed lookup"}
case BadOrigin:
return &DispatchOutcomeError{"bad origin"}
err = &DispatchOutcomeError{"bad origin"}
case Module:
return &DispatchOutcomeError{fmt.Sprintf("custom module error: %s", val)}
err = &DispatchOutcomeError{fmt.Sprintf("custom module error: %s", val)}
case Call:
return &TransactionValidityError{errUnexpectedTxCall}
err = &TransactionValidityError{errUnexpectedTxCall}
case Payment:
return &TransactionValidityError{errInvalidPayment}
err = &TransactionValidityError{errInvalidPayment}
case Future:
return &TransactionValidityError{errInvalidTransaction}
err = &TransactionValidityError{errInvalidTransaction}
case Stale:
return &TransactionValidityError{errOutdatedTransaction}
err = &TransactionValidityError{errOutdatedTransaction}
case BadProof:
return &TransactionValidityError{errBadProof}
err = &TransactionValidityError{errBadProof}
case AncientBirthBlock:
return &TransactionValidityError{errAncientBirthBlock}
err = &TransactionValidityError{errAncientBirthBlock}
case ExhaustsResources:
return &TransactionValidityError{errExhaustsResources}
err = &TransactionValidityError{errExhaustsResources}
case InvalidCustom:
return &TransactionValidityError{newUnknownError(val)}
err = &TransactionValidityError{newUnknownError(val)}
case BadMandatory:
return &TransactionValidityError{errMandatoryDispatchError}
err = &TransactionValidityError{errMandatoryDispatchError}
case MandatoryDispatch:
return &TransactionValidityError{errInvalidMandatoryDispatch}
err = &TransactionValidityError{errInvalidMandatoryDispatch}
case ValidityCannotLookup:
return &TransactionValidityError{errLookupFailed}
err = &TransactionValidityError{errLookupFailed}
case NoUnsignedValidator:
return &TransactionValidityError{errValidatorNotFound}
err = &TransactionValidityError{errValidatorNotFound}
case UnknownCustom:
return &TransactionValidityError{newUnknownError(val)}
err = &TransactionValidityError{newUnknownError(val)}
case BadSigner:
err = &TransactionValidityError{errBadSigner}
default:
err = errInvalidResult
}

return errInvalidResult
return err
}

func determineErr(res []byte) error {
dispatchError := scale.MustNewVaryingDataType(other, CannotLookup{}, BadOrigin{}, Module{})
invalid := scale.MustNewVaryingDataType(Call{}, Payment{}, Future{}, Stale{}, BadProof{}, AncientBirthBlock{},
ExhaustsResources{}, invalidCustom, BadMandatory{}, MandatoryDispatch{})
ExhaustsResources{}, invalidCustom, BadMandatory{}, MandatoryDispatch{}, BadSigner{})
unknown := scale.MustNewVaryingDataType(ValidityCannotLookup{}, NoUnsignedValidator{}, unknownCustom)

okRes := scale.NewResult(nil, dispatchError)
Expand Down
14 changes: 10 additions & 4 deletions lib/runtime/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ const (
// v0.9.29 polkadot
POLKADOT_RUNTIME_v0929 = "polkadot_runtime-v929"
POLKADOT_RUNTIME_V0929_FP = "polkadot_runtime-v929.compact.wasm"
POLKADOT_RUNTIME_V0929_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9." +
"29/polkadot_runtime-v9290.compact.compressed.wasm?raw=true"
POLKADOT_RUNTIME_V0929_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9.29/" +
"polkadot_runtime-v9290.compact.compressed.wasm?raw=true"

// v0.9.29 westend
WESTEND_RUNTIME_v0929 = "westend_runtime-v929"
WESTEND_RUNTIME_V0929_FP = "westend_runtime-v929.compact.wasm"
WESTEND_RUNTIME_V0929_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9." +
"29/westend_runtime-v9290.compact.compressed.wasm?raw=true"
WESTEND_RUNTIME_V0929_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9.29/" +
"westend_runtime-v9290.compact.compressed.wasm?raw=true"

// v0.9.12 westend used for zero-address bug test
WESTEND_RUNTIME_v0912 = "westend_runtime-v9111"
WESTEND_RUNTIME_V0912_FP = "westend_runtime-v9111.compact.wasm"
WESTEND_RUNTIME_V0912_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9.11/" +
"westend_runtime-v9111.compact.compressed.wasm?raw=true"
)

const (
Expand Down
20 changes: 15 additions & 5 deletions lib/runtime/invalid_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,8 @@ func (i InvalidTransaction) Error() string { //skipcq: GO-W1029

// NewInvalidTransaction is constructor for InvalidTransaction
func NewInvalidTransaction() InvalidTransaction {
vdt, err := scale.NewVaryingDataType(Call{}, Payment{}, Future{}, Stale{}, BadProof{}, AncientBirthBlock{},
ExhaustsResources{}, InvalidCustom(0), BadMandatory{}, MandatoryDispatch{})
if err != nil {
panic(err)
}
vdt := scale.MustNewVaryingDataType(Call{}, Payment{}, Future{}, Stale{}, BadProof{}, AncientBirthBlock{},
ExhaustsResources{}, InvalidCustom(0), BadMandatory{}, MandatoryDispatch{}, BadSigner{})
return InvalidTransaction(vdt)
}

Expand Down Expand Up @@ -188,3 +185,16 @@ func (m MandatoryDispatch) String() string { return m.Error() }
func (MandatoryDispatch) Error() string {
return "invalid mandatory dispatch"
}

// BadSigner A transaction with a mandatory dispatch
type BadSigner struct{}

// Index returns VDT index
func (BadSigner) Index() uint { return 10 }

func (b BadSigner) String() string { return b.Error() }

// Error returns the error message associated with the MandatoryDispatch
func (BadSigner) Error() string {
return "invalid signing address"
}
8 changes: 6 additions & 2 deletions lib/runtime/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func GetRuntime(ctx context.Context, runtime string) (
return runtime, nil
}

basePath := filepath.Join(os.TempDir(), "/gossamer/runtimes/")
basePath := filepath.Join(os.TempDir(), "gossamer", "runtimes")
const perm = os.FileMode(0777)
err = os.MkdirAll(basePath, perm)
if err != nil {
Expand All @@ -78,6 +78,10 @@ func GetRuntime(ctx context.Context, runtime string) (
case WESTEND_RUNTIME_v0929:
runtimeFilename = WESTEND_RUNTIME_V0929_FP
url = WESTEND_RUNTIME_V0929_URL
// only used for TestInstance_BadSignatureExtrinsic_On_WestendBlock8077850
case WESTEND_RUNTIME_v0912:
runtimeFilename = WESTEND_RUNTIME_V0912_FP
url = WESTEND_RUNTIME_V0912_URL
default:
return "", fmt.Errorf("%w: %s", ErrRuntimeUnknown, runtime)
}
Expand All @@ -96,7 +100,7 @@ func GetRuntime(ctx context.Context, runtime string) (
ctx, cancel := context.WithTimeout(ctx, requestTimeout)
defer cancel()

request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
if err != nil {
return "", fmt.Errorf("cannot make HTTP request: %w", err)
}
Expand Down
20 changes: 15 additions & 5 deletions lib/runtime/wazero/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package wazero_runtime

import (
"bytes"
"context"
"crypto/rand"
"encoding/binary"
Expand Down Expand Up @@ -32,7 +33,8 @@ var (
log.AddContext("module", "wazero"),
)

noneEncoded []byte = []byte{0x00}
noneEncoded []byte = []byte{0x00}
allZeroesBytes = [32]byte{}
)

const (
Expand Down Expand Up @@ -710,6 +712,18 @@ func ext_crypto_sr25519_verify_version_2(ctx context.Context, m api.Module, sig
panic("nil runtime context")
}

pubKeyBytes, ok := m.Memory().Read(key, 32)
if !ok {
panic("read overflow")
}

// prevents Polkadot zero-address crash using
// ext_crypto_sr25519_verify_version_1
// https://pacna.org/dot-zero-addr/
if bytes.Equal(pubKeyBytes, allZeroesBytes[:]) {
return ext_crypto_sr25519_verify_version_1(ctx, m, sig, msg, key)
}

sigVerifier := rtCtx.SigVerifier

message := read(m, msg)
Expand All @@ -718,10 +732,6 @@ func ext_crypto_sr25519_verify_version_2(ctx context.Context, m api.Module, sig
panic("read overflow")
}

pubKeyBytes, ok := m.Memory().Read(key, 32)
if !ok {
panic("read overflow")
}
pub, err := sr25519.NewPublicKey(pubKeyBytes)
if err != nil {
logger.Error("invalid sr25519 public key")
Expand Down
132 changes: 132 additions & 0 deletions lib/runtime/wazero/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package wazero_runtime

import (
_ "embed"

"bytes"
"encoding/json"
"math/big"
Expand Down Expand Up @@ -403,6 +405,136 @@ func TestInstance_BabeConfiguration_WestendRuntime_NoAuthorities(t *testing.T) {
require.Equal(t, expected, cfg)
}

func TestInstance_BadSignature_WestendBlock8077850(t *testing.T) {
tests := map[string]struct {
setupRuntime func(t *testing.T) (*Instance, *types.Header)
expectedError []byte
}{
"westend_dev_runtime_should_fail_with_bad_signature": {
expectedError: []byte{1, 0, 0xa},
setupRuntime: func(t *testing.T) (*Instance, *types.Header) {
genesisPath := utils.GetWestendDevRawGenesisPath(t)
gen := genesisFromRawJSON(t, genesisPath)
genTrie, err := runtime.NewTrieFromGenesis(gen)
require.NoError(t, err)

// set state to genesis state
genState := storage.NewTrieState(&genTrie)

cfg := Config{
Storage: genState,
LogLvl: log.Critical,
}

rt, err := NewRuntimeFromGenesis(cfg)
require.NoError(t, err)

// reset state back to parent state before executing
parentState := storage.NewTrieState(&genTrie)
rt.SetContextStorage(parentState)

genesisHeader := &types.Header{
Number: 0,
StateRoot: genTrie.MustHash(),
}

header := &types.Header{
ParentHash: genesisHeader.Hash(),
Number: 1,
Digest: types.NewDigest(),
}

return rt, header
},
},
"westend_0912_runtime_should_fail_with_invalid_payment": {
expectedError: []byte{1, 0, 1},
setupRuntime: func(t *testing.T) (*Instance, *types.Header) {
genesisPath := utils.GetWestendDevRawGenesisPath(t)
gen := genesisFromRawJSON(t, genesisPath)
genTrie, err := runtime.NewTrieFromGenesis(gen)
require.NoError(t, err)

rt := NewTestInstance(t, runtime.WESTEND_RUNTIME_v0912)
parentState := storage.NewTrieState(&genTrie)
rt.SetContextStorage(parentState)

genesisHeader := &types.Header{
Number: 0,
StateRoot: genTrie.MustHash(),
}

header := &types.Header{
ParentHash: genesisHeader.Hash(),
Number: 1,
Digest: types.NewDigest(),
}

return rt, header
},
},
}

for tname, tt := range tests {
tt := tt

t.Run(tname, func(t *testing.T) {
instance, header := tt.setupRuntime(t)

err := instance.InitializeBlock(header)
require.NoError(t, err)

idata := types.NewInherentData()
err = idata.SetInherent(types.Timstap0, uint64(5))
require.NoError(t, err)

err = idata.SetInherent(types.Babeslot, uint64(1))
require.NoError(t, err)

ienc, err := idata.Encode()
require.NoError(t, err)

// Call BlockBuilder_inherent_extrinsics which returns the inherents as encoded extrinsics
inherentExts, err := instance.InherentExtrinsics(ienc)
require.NoError(t, err)

// decode inherent extrinsics
cp := make([]byte, len(inherentExts))
copy(cp, inherentExts)
var inExts [][]byte
err = scale.Unmarshal(cp, &inExts)
require.NoError(t, err)

// apply each inherent extrinsic
for _, inherent := range inExts {
in, err := scale.Marshal(inherent)
require.NoError(t, err)

ret, err := instance.ApplyExtrinsic(in)
require.NoError(t, err)
require.Equal(t, ret, []byte{0, 0})
}

keyring, err := signature.KeyringPairFromSecret(
"0x00000000000000000000000000000000000000000000000000000"+
"00000000000000000000000000000000000000000000000000000"+
"0000000000000000000000", 42)
require.NoError(t, err)

extHex := runtime.NewTestExtrinsic(t, instance, header.ParentHash, header.ParentHash,
0, keyring, "System.remark", []byte{0xab, 0xcd})

res, err := instance.ApplyExtrinsic(common.MustHexToBytes(extHex))
require.NoError(t, err)

// should fail with transaction validity error: invalid payment for runtime 0.9.12
// should fail with transaction validity error: bad signature for runtime version greater than 0.9.12
require.Equal(t, tt.expectedError, res)
})
}

}

func TestInstance_BabeConfiguration_WestendRuntime_WithAuthorities(t *testing.T) {
tt := trie.NewEmptyTrie()

Expand Down

0 comments on commit 8b93d5e

Please sign in to comment.