Skip to content

Commit 8d69091

Browse files
authored
Merge branch 'master' into nightly-ci
2 parents db9c1c5 + 7c4c464 commit 8d69091

File tree

12 files changed

+68
-11
lines changed

12 files changed

+68
-11
lines changed

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ Description of the upcoming release here.
1111

1212
### Added
1313

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

1716
### Changed
1817

1918
- [#305](https://github.com/FuelLabs/sway-libs/pull/305) Updates to forc `v0.66.2`, fuel-core `v0.40.0`, and fuels-rs `v0.66.9`.
2019
- [#306](https://github.com/FuelLabs/sway-libs/pull/306) Updates the SRC-7 naming to Onchain Native Asset Metadata Standard.
20+
- [#308](https://github.com/FuelLabs/sway-libs/pull/308) Removes comments on Cross-Contract Reentrancy vulnerability.
2121

2222
### Fixed
2323

docs/book/src/reentrancy/index.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22

33
The Reentrancy Guard Library provides an API to check for and disallow reentrancy on a contract. A reentrancy attack happens when a function is externally invoked during its execution, allowing it to be run multiple times in a single transaction.
44

5-
The reentrancy check is used to check if a contract ID has been called more than
6-
once in the current call stack.
5+
The reentrancy check is used to check if a contract ID has been called more than once in the current call stack.
76

87
A reentrancy, or "recursive call" attack can cause some functions to behave in unexpected ways. This can be prevented by asserting a contract has not yet been called in the current transaction. An example can be found [here](https://swcregistry.io/docs/SWC-107).
98

109
For implementation details on the Reentrancy Guard Library please see the [Sway Libs Docs](https://fuellabs.github.io/sway-libs/master/sway_libs/reentrancy/index.html).
1110

12-
## Known Issues
13-
14-
While this can protect against both single-function reentrancy and cross-function reentrancy attacks, it WILL NOT PREVENT a cross-contract reentrancy attack.
15-
1611
## Importing the Reentrancy Guard Library
1712

1813
In order to use the Reentrancy Guard library, Sway Libs must be added to the `Forc.toml` file and then imported into your Sway project. To add Sway Libs as a dependency to the `Forc.toml` file in your project please see the [Getting Started](../getting_started/index.md).
@@ -45,3 +40,7 @@ To check if the current caller is a reentrant, you may call the `is_reentrant()`
4540
```sway
4641
{{#include ../../../../examples/reentrancy/src/main.sw:is_reentrant}}
4742
```
43+
44+
## Cross Contract Reentrancy
45+
46+
Cross-Contract Reentrancy is not possible on Fuel due to the use of Native Assets. As such, no contract calls are performed when assets are transferred. However standard security practices when relying on other contracts for state should still be applied, especially when making external calls.

libs/src/reentrancy.sw

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ use std::registers::frame_ptr;
1515
///
1616
/// Not needed if the Checks-Effects-Interactions (CEI) pattern is followed (as prompted by the
1717
/// compiler).
18-
/// > Caution: While this can protect against both single-function reentrancy and cross-function
19-
/// reentrancy attacks, it WILL NOT PREVENT a cross-contract reentrancy attack.
2018
///
2119
/// # Examples
2220
///

tests/Forc.lock

+6
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ dependencies = [
183183
"std",
184184
]
185185

186+
[[package]]
187+
name = "reentrancy_fallback_abi"
188+
source = "path+from-root-F53252C7DB7025EE"
189+
dependencies = ["std"]
190+
186191
[[package]]
187192
name = "reentrancy_target_abi"
188193
source = "member"
@@ -193,6 +198,7 @@ name = "reentrancy_target_contract"
193198
source = "member"
194199
dependencies = [
195200
"reentrancy_attacker_abi",
201+
"reentrancy_fallback_abi",
196202
"reentrancy_target_abi",
197203
"std",
198204
"sway_libs",

tests/src/reentrancy/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,20 @@ mod revert {
161161
.await
162162
.unwrap();
163163
}
164+
165+
#[tokio::test]
166+
#[should_panic(expected = "NonReentrant")]
167+
async fn can_block_fallback_reentrancy() {
168+
let wallet = launch_provider_and_get_wallet().await.unwrap();
169+
let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await;
170+
let (instance, target_id) = get_target_instance(wallet).await;
171+
172+
attacker_instance
173+
.methods()
174+
.launch_thwarted_attack_4(target_id)
175+
.with_contracts(&[&instance])
176+
.call()
177+
.await
178+
.unwrap();
179+
}
164180
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[project]
2+
authors = ["Fuel Labs <contact@fuel.sh>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "reentrancy_fallback_abi"
6+
7+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
library;
2+
3+
abi FallbackAttack {
4+
fn nonexistant_function(contract_id: ContractId);
5+
}

tests/src/reentrancy/reentrancy_attacker_abi/src/main.sw

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ abi Attacker {
66
fn launch_thwarted_attack_2(target: ContractId);
77
#[storage(write)]
88
fn launch_thwarted_attack_3(target: ContractId, helper: ContractId);
9+
fn launch_thwarted_attack_4(target: ContractId);
910
fn innocent_call(target: ContractId);
1011
fn evil_callback_1() -> bool;
1112
fn evil_callback_2();

tests/src/reentrancy/reentrancy_attacker_contract/src/main.sw

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
contract;
22

3-
use std::auth::*;
3+
use std::{auth::*, call_frames::*,};
44

55
use reentrancy_target_abi::Target;
66
use reentrancy_attacker_abi::Attacker;
@@ -41,6 +41,10 @@ impl Attacker for Contract {
4141
.cross_contract_reentrancy_denied();
4242
}
4343

44+
fn launch_thwarted_attack_4(target: ContractId) {
45+
abi(Target, target.bits()).fallback_contract_call();
46+
}
47+
4448
fn innocent_call(target: ContractId) {
4549
abi(Target, target.bits()).guarded_function_is_callable();
4650
}
@@ -71,3 +75,11 @@ impl Attacker for Contract {
7175

7276
fn innocent_callback() {}
7377
}
78+
79+
#[fallback]
80+
fn fallback() {
81+
let call_args = called_args::<ContractId>();
82+
83+
let target_abi = abi(Target, call_args.bits());
84+
target_abi.fallback_contract_call();
85+
}

tests/src/reentrancy/reentrancy_target_abi/src/main.sw

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ abi Target {
77
fn intra_contract_call();
88
fn guarded_function_is_callable();
99
fn cross_contract_reentrancy_denied();
10+
fn fallback_contract_call();
1011
}

tests/src/reentrancy/reentrancy_target_contract/Forc.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ name = "reentrancy_target_contract"
66

77
[dependencies]
88
reentrancy_attacker_abi = { path = "../reentrancy_attacker_abi" }
9+
reentrancy_fallback_abi = { path = "../reentrancy_attack_fallback_abi" }
910
reentrancy_target_abi = { path = "../reentrancy_target_abi" }
1011
sway_libs = { path = "../../../../libs" }

tests/src/reentrancy/reentrancy_target_contract/src/main.sw

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use sway_libs::reentrancy::*;
55

66
use reentrancy_attacker_abi::Attacker;
77
use reentrancy_target_abi::Target;
8+
use reentrancy_fallback_abi::FallbackAttack;
89

910
// Return the sender as a ContractId or panic:
1011
pub fn get_msg_sender_id_or_panic() -> ContractId {
@@ -63,4 +64,14 @@ impl Target for Contract {
6364
.bits())
6465
.evil_callback_4();
6566
}
67+
68+
fn fallback_contract_call() {
69+
// panic if reentrancy detected
70+
reentrancy_guard();
71+
72+
// this call transfers control to the attacker contract, allowing it to execute arbitrary code.
73+
abi(FallbackAttack, get_msg_sender_id_or_panic()
74+
.bits())
75+
.nonexistant_function(ContractId::this());
76+
}
6677
}

0 commit comments

Comments
 (0)