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

fix(lib/runtime): prevents polkadot zero-address bug using sr25519_verify version 1 #3494

Merged
merged 15 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
13 changes: 12 additions & 1 deletion 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,6 +272,14 @@ func (MandatoryDispatch) Index() uint { return 9 }

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

// MandatoryDispatch 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) error {
vdtVal, err := vdt.Value()
if err != nil {
Expand Down Expand Up @@ -311,6 +320,8 @@ func determineErrType(vdt scale.VaryingDataType) error {
return &TransactionValidityError{errValidatorNotFound}
case UnknownCustom:
return &TransactionValidityError{newUnknownError(val)}
case BadSigner:
return &TransactionValidityError{errBadSigner}
}

return errInvalidResult
Expand All @@ -319,7 +330,7 @@ func determineErrType(vdt scale.VaryingDataType) error {
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
4 changes: 4 additions & 0 deletions lib/runtime/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 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