Skip to content

Commit

Permalink
Add fallback tests to reentrancy guard library (#309)
Browse files Browse the repository at this point in the history
## Type of change

<!--Delete points that do not apply-->

- New feature

## Changes

The following changes have been made:

- Adds tests to the reentrancy guard library using the `fallback`
function, ensuring the reentrancy guard still works

## Notes

- This Pr compliments #307 

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
- [x] I have updated the changelog to reflect the changes on this PR.

---------

Co-authored-by: K1-R1 <[email protected]>
  • Loading branch information
bitzoic and K1-R1 authored Nov 29, 2024
1 parent f901f50 commit 7c4c464
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 3 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ Description of the upcoming release here.

### Added

- Something new here 1
- Something new here 2
- [#309](https://github.com/FuelLabs/sway-libs/pull/309) Adds fallback function test cases to the Reentrancy Guard Library.

### Changed

Expand Down
6 changes: 6 additions & 0 deletions tests/Forc.lock
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ dependencies = [
"std",
]

[[package]]
name = "reentrancy_fallback_abi"
source = "path+from-root-F53252C7DB7025EE"
dependencies = ["std"]

[[package]]
name = "reentrancy_target_abi"
source = "member"
Expand All @@ -193,6 +198,7 @@ name = "reentrancy_target_contract"
source = "member"
dependencies = [
"reentrancy_attacker_abi",
"reentrancy_fallback_abi",
"reentrancy_target_abi",
"std",
"sway_libs",
Expand Down
16 changes: 16 additions & 0 deletions tests/src/reentrancy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,20 @@ mod revert {
.await
.unwrap();
}

#[tokio::test]
#[should_panic(expected = "NonReentrant")]
async fn can_block_fallback_reentrancy() {
let wallet = launch_provider_and_get_wallet().await.unwrap();
let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await;
let (instance, target_id) = get_target_instance(wallet).await;

attacker_instance
.methods()
.launch_thwarted_attack_4(target_id)
.with_contracts(&[&instance])
.call()
.await
.unwrap();
}
}
7 changes: 7 additions & 0 deletions tests/src/reentrancy/reentrancy_attack_fallback_abi/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "reentrancy_fallback_abi"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
library;

abi FallbackAttack {
fn nonexistant_function(contract_id: ContractId);
}
1 change: 1 addition & 0 deletions tests/src/reentrancy/reentrancy_attacker_abi/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ abi Attacker {
fn launch_thwarted_attack_2(target: ContractId);
#[storage(write)]
fn launch_thwarted_attack_3(target: ContractId, helper: ContractId);
fn launch_thwarted_attack_4(target: ContractId);
fn innocent_call(target: ContractId);
fn evil_callback_1() -> bool;
fn evil_callback_2();
Expand Down
14 changes: 13 additions & 1 deletion tests/src/reentrancy/reentrancy_attacker_contract/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
contract;

use std::auth::*;
use std::{auth::*, call_frames::*,};

use reentrancy_target_abi::Target;
use reentrancy_attacker_abi::Attacker;
Expand Down Expand Up @@ -41,6 +41,10 @@ impl Attacker for Contract {
.cross_contract_reentrancy_denied();
}

fn launch_thwarted_attack_4(target: ContractId) {
abi(Target, target.bits()).fallback_contract_call();
}

fn innocent_call(target: ContractId) {
abi(Target, target.bits()).guarded_function_is_callable();
}
Expand Down Expand Up @@ -71,3 +75,11 @@ impl Attacker for Contract {

fn innocent_callback() {}
}

#[fallback]
fn fallback() {
let call_args = called_args::<ContractId>();

let target_abi = abi(Target, call_args.bits());
target_abi.fallback_contract_call();
}
1 change: 1 addition & 0 deletions tests/src/reentrancy/reentrancy_target_abi/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ abi Target {
fn intra_contract_call();
fn guarded_function_is_callable();
fn cross_contract_reentrancy_denied();
fn fallback_contract_call();
}
1 change: 1 addition & 0 deletions tests/src/reentrancy/reentrancy_target_contract/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ name = "reentrancy_target_contract"

[dependencies]
reentrancy_attacker_abi = { path = "../reentrancy_attacker_abi" }
reentrancy_fallback_abi = { path = "../reentrancy_attack_fallback_abi" }
reentrancy_target_abi = { path = "../reentrancy_target_abi" }
sway_libs = { path = "../../../../libs" }
11 changes: 11 additions & 0 deletions tests/src/reentrancy/reentrancy_target_contract/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use sway_libs::reentrancy::*;

use reentrancy_attacker_abi::Attacker;
use reentrancy_target_abi::Target;
use reentrancy_fallback_abi::FallbackAttack;

// Return the sender as a ContractId or panic:
pub fn get_msg_sender_id_or_panic() -> ContractId {
Expand Down Expand Up @@ -63,4 +64,14 @@ impl Target for Contract {
.bits())
.evil_callback_4();
}

fn fallback_contract_call() {
// panic if reentrancy detected
reentrancy_guard();

// this call transfers control to the attacker contract, allowing it to execute arbitrary code.
abi(FallbackAttack, get_msg_sender_id_or_panic()
.bits())
.nonexistant_function(ContractId::this());
}
}

0 comments on commit 7c4c464

Please sign in to comment.