Skip to content

Commit

Permalink
pytest -v -s test_ica.py::test_exploit
Browse files Browse the repository at this point in the history
  • Loading branch information
mmsqe committed Dec 8, 2023
1 parent 95da881 commit 18754a6
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 32 deletions.
2 changes: 1 addition & 1 deletion integration_tests/configs/ibc.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ config {
app_state+: {
cronos+: {
params+: {
max_callback_gas: 50000,
max_callback_gas: 4000000,
},
},
feemarket+: {
Expand Down
49 changes: 49 additions & 0 deletions integration_tests/contracts/contracts/TestICAExploit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract TestICAExploit {
uint256 max = 1;
uint64 lastSeq;
// sha256('cronos-evm')[:20]
address constant module_address = 0x89A7EF2F08B1c018D5Cc88836249b84Dd5392905;
enum Status {
NONE,
SUCCESS,
FAIL
}
mapping (uint256 => mapping (uint256 => string[])) public dataMap;
mapping (string => mapping (uint64 => Status)) public statusMap;
event OnPacketResult(string indexed packetSrcChannel, uint64 seq, Status status);

function getStatus(string calldata packetSrcChannel, uint64 seq) public view returns (Status) {
return statusMap[packetSrcChannel][seq];
}

function onPacketResultCallback(string calldata packetSrcChannel, uint64 seq, bool ack) external payable returns (bool) {
// To prevent called by arbitrary user
require(msg.sender == module_address);
Status status = Status.FAIL;
if (ack) {
status = Status.SUCCESS;
}
statusMap[packetSrcChannel][seq] = status;
emit OnPacketResult(packetSrcChannel, seq, status);
for (uint256 n = 0; n < max; n++) {
innerLoop(n, packetSrcChannel);
}
return true;
}

function innerLoop(uint256 iterations, string calldata packetSrcChannel) private {
if (iterations == 0) {
return;
}
for (uint256 i = 0; i < max; i++) {
string[] memory list = new string[](max);
for (uint256 j = 0; j < max; j++) {
list[j] = packetSrcChannel;
}
dataMap[iterations][i] = list;
}
}
}
92 changes: 64 additions & 28 deletions integration_tests/test_ica.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,30 @@ def ibc(request, tmp_path_factory):
)


def test_ica(ibc, tmp_path):
def generated_tx_packet(cli_controller, msg, msg_num, memo):
msgs = []
for _ in range(msg_num):
msgs.append(msg)
data = json.dumps(msgs)
packet = cli_controller.ica_generate_packet_data(data, json.dumps(memo))
return packet


def send_tx(cli_host, cli_controller, ica_address, connid, msg, msg_num, memo):
num_txs = len(cli_host.query_all_txs(ica_address)["txs"])
generated_tx = json.dumps(generated_tx_packet(cli_controller, msg, msg_num, memo))
# submit transaction on host chain on behalf of interchain account
rsp = cli_controller.ica_ctrl_send_tx(
connid,
generated_tx,
gas="200000",
from_="signer2",
)
assert rsp["code"] == 0, rsp["raw_log"]
wait_for_check_tx(cli_host, ica_address, num_txs)


def test_exploit(ibc, tmp_path):
connid = "connection-0"
cli_host = ibc.chainmain.cosmos_cli()
cli_controller = ibc.cronos.cosmos_cli()
Expand All @@ -39,35 +62,49 @@ def test_ica(ibc, tmp_path):
to = cli_host.address("signer2")
amount = 1000
denom = "basecro"
jsonfile = CONTRACTS["TestICA"]
jsonfile = CONTRACTS["TestICAExploit"]
tcontract = deploy_contract(ibc.cronos.w3, jsonfile)
memo = {"src_callback": {"address": tcontract.address}}
msg = gen_send_msg(ica_address, to, denom, amount)

def generated_tx_packet(msg_num):
# generate a transaction to send to host chain
m = gen_send_msg(ica_address, to, denom, amount)
msgs = []
for i in range(msg_num):
msgs.append(m)
data = json.dumps(msgs)
packet = cli_controller.ica_generate_packet_data(data, json.dumps(memo))
return packet
msg_num = 1
send_tx(cli_host, cli_controller, ica_address, connid, msg, msg_num, memo)
balance -= amount * msg_num
assert cli_host.balance(ica_address, denom=denom) == balance

def send_tx(msg_num, gas="200000"):
num_txs = len(cli_host.query_all_txs(ica_address)["txs"])
generated_tx = json.dumps(generated_tx_packet(msg_num))
# submit transaction on host chain on behalf of interchain account
rsp = cli_controller.ica_ctrl_send_tx(
connid,
generated_tx,
gas=gas,
from_="signer2",
)
assert rsp["code"] == 0, rsp["raw_log"]
wait_for_check_tx(cli_host, ica_address, num_txs)
def check_for_ack():
criteria = "message.action=/ibc.core.channel.v1.MsgAcknowledgement"
return cli_controller.tx_search(criteria)["txs"]

txs = wait_for_fn("ack change", check_for_ack)
print("mm-txs", txs[0]["gas_used"])

last_seq = 1

def check_for_status():
status = tcontract.caller.getStatus(channel_id, last_seq)
print("mm-status", status)
return status

msg_num = 10
send_tx(msg_num)
txs = wait_for_fn("status change", check_for_status)
print("mm-last_seq", last_seq)


def test_ica(ibc, tmp_path):
connid = "connection-0"
cli_host = ibc.chainmain.cosmos_cli()
cli_controller = ibc.cronos.cosmos_cli()
ica_address, channel_id = register_acc(cli_controller, connid)
balance = funds_ica(cli_host, ica_address)
to = cli_host.address("signer2")
amount = 1000
denom = "basecro"
jsonfile = CONTRACTS["TestICA"]
tcontract = deploy_contract(ibc.cronos.w3, jsonfile)
memo = {"src_callback": {"address": tcontract.address}}
msg = gen_send_msg(ica_address, to, denom, amount)
msg_num = 1
send_tx(cli_host, cli_controller, ica_address, connid, msg, msg_num, memo)
balance -= amount * msg_num
assert cli_host.balance(ica_address, denom=denom) == balance

Expand All @@ -83,10 +120,9 @@ def check_for_ack():
def generated_tx_txt(msg_num):
# generate a transaction to send to host chain
generated_tx = tmp_path / "generated_tx.txt"
m = gen_send_msg(ica_address, to, denom, amount)
msgs = []
for i in range(msg_num):
msgs.append(m)
for _ in range(msg_num):
msgs.append(msg)
generated_tx_msg = {
"body": {
"messages": msgs,
Expand Down
1 change: 1 addition & 0 deletions integration_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"CosmosERC20": "CosmosToken.sol",
"TestBank": "TestBank.sol",
"TestICA": "TestICA.sol",
"TestICAExploit": "TestICAExploit.sol",
}


Expand Down
2 changes: 1 addition & 1 deletion scripts/run-integration-tests
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ cd ../integration_tests/contracts
HUSKY_SKIP_INSTALL=1 npm install
npm run typechain
cd ..
nix-shell --run "pytest -vv -s"
nix-shell --run "pytest -v -s test_ica.py::test_exploit"
5 changes: 3 additions & 2 deletions x/cronos/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,15 @@ func (k Keeper) onPacketResult(
senderAddr := common.BytesToAddress(sender)
contractAddr := common.HexToAddress(contractAddress)
if senderAddr != contractAddr {
return fmt.Errorf("sender is not authenticated: expected %s, got %s", senderAddr, contractAddr)
// return fmt.Errorf("sender is not authenticated: expected %s, got %s", senderAddr, contractAddr)
fmt.Printf("sender is not authenticated: expected %s, got %s\n", senderAddr, contractAddr)
}
data, err := cronosprecompiles.OnPacketResultCallback(packet.SourceChannel, packet.Sequence, acknowledgement)
if err != nil {
return err
}
gasLimit := k.GetParams(ctx).MaxCallbackGas
_, _, err = k.CallEVM(ctx, &senderAddr, data, big.NewInt(0), gasLimit)
_, _, err = k.CallEVM(ctx, &contractAddr, data, big.NewInt(0), gasLimit)
return err
}

Expand Down

0 comments on commit 18754a6

Please sign in to comment.