From d9bf37fd989ffe97442c39b48af5a8350df81193 Mon Sep 17 00:00:00 2001 From: tomasavola Date: Mon, 29 Apr 2024 11:44:15 -0300 Subject: [PATCH 01/30] Add vulnerability documentation for dos-unbounded-operation #185 --- .../11-dos-unbounded-operation.md | 43 ++++++++++++++ ...ted-update-current-contract-wasm (copy).md | 59 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 docs/docs/vulnerabilities/11-dos-unbounded-operation.md create mode 100644 docs/docs/vulnerabilities/6-unprotected-update-current-contract-wasm (copy).md diff --git a/docs/docs/vulnerabilities/11-dos-unbounded-operation.md b/docs/docs/vulnerabilities/11-dos-unbounded-operation.md new file mode 100644 index 00000000..34f7d036 --- /dev/null +++ b/docs/docs/vulnerabilities/11-dos-unbounded-operation.md @@ -0,0 +1,43 @@ +# DoS unbounded operation +## Description +- Vulnerability Category: `Denial of Service` +- Severity: `Medium` +- Detectors: [`dos-unbounded-operation`](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/dos-unbounded-operation) +- Test Cases: [`dos-unbounded-operation-3`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/dos-unbounded-operation/dos-unbounded-operation-3) + +Each block in a Stellar Blockchain has an upper bound on the amount of gas that can be spent, and thus the amount computation that can be done. This is the Block Gas Limit. If the gas spent exceeds this limit, the transaction will fail. + +In this smart contract a malicious user may modify the smart contract's conditions so that any transaction coming after will fail, thus imposing a denial of service for other users. + +## Exploit Scenario +In the following example, a contract has a function ´unsafe_loop_with_array´, which contains a for loop that iterates over a range of numbers from 0 to the lenght of the array ´unknown_array´. The issue is that if the length of the array is extremely large, it would cause the loop to execute many times, potentially leading to an unusable state of the contract. + +```rust + pub fn unsafe_loop_with_array(unknown_array: BytesN<8>) -> u32 { + let mut sum = 0; + for i in 0..unknown_array.len() { + sum += i; + } + sum + } +``` +The vulnerable code example can be found [here](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/dos-unbounded-operation/dos-unbounded-operation-3/vulnerable-example). + +## Remediation +To solve this, instead of relying on an external parameter, we should introduce a known value directly into the loop. + +```rust + pub fn safe_loop_with_array() -> u64 { + let mut sum = 0; + let known_array = [0; 8]; + for i in 0..known_array.len() { + sum += i; + } + sum as u64 + } +``` +The remediated code example can be found [here](https://github.com/CoinFabrik/scout-soroban/blob/main/test-cases/dos-unbounded-operation/dos-unbounded-operation-3/remediated-example/lib.rs). + +## References +- https://consensys.github.io/smart-contract-best-practices/attacks/denial-of-service +- https://consensys.github.io/smart-contract-best-practices/development-recommendations/general/external-calls/ diff --git a/docs/docs/vulnerabilities/6-unprotected-update-current-contract-wasm (copy).md b/docs/docs/vulnerabilities/6-unprotected-update-current-contract-wasm (copy).md new file mode 100644 index 00000000..803af1db --- /dev/null +++ b/docs/docs/vulnerabilities/6-unprotected-update-current-contract-wasm (copy).md @@ -0,0 +1,59 @@ +# Unprotected update current contract wasm + +## Description + +- Vulnerability Category: `Authorization` +- Vulnerability Severity: `Critical` +- Detectors: [`unprotected-update-current-contract-wasm`](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/unprotected-update-current-contract-wasm) +- Test Cases: [`unprotected-update-current-contract-wasm-1`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unprotected-update-current-contract-wasm/unprotected-update-current-contract-wasm-1) [`unprotected-update-current-contract-wasm-2`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unprotected-update-current-contract-wasm/unprotected-update-current-contract-wasm-2) + +It warns you if `update_current_contract_wasm()` function is called without a previous check of the address of the caller. If users are allowed to call `update_current_contract_wasm()`, they can intentionally modify the contract behaviour, leading to the loss of all associated data/tokens and functionalities given by this contract or by others that depend on it. + +## Exploit Scenario + +Consider the following `Soroban` contract: + +```rust +#[contractimpl] +impl UpgradeableContract { + pub fn init(e: Env, admin: Address) { + e.storage().instance().set(&DataKey::Admin, &admin); + } + + pub fn version() -> u32 { + 1 + } + + pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) { + e.deployer().update_current_contract_wasm(new_wasm_hash); + } +} +``` +This contract allows upgrades through the `update_current_contract_wasm` function. If just anyone can call this function, they could modify the contract behaviour. + +The vulnerable code example can be found [`here`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unprotected-update-current-contract-wasm/unprotected-update-current-contract-wasm-1/vulnerable-example). + +## Remediation + +To prevent this, the function should be restricted to administrators or authorized users only. + +```rust +#[contractimpl] +impl UpgradeableContract { + pub fn init(e: Env, admin: Address) { + e.storage().instance().set(&DataKey::Admin, &admin); + } + + pub fn version() -> u32 { + 1 + } + + pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) { + let admin: Address = e.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + e.deployer().update_current_contract_wasm(new_wasm_hash); + } +} +``` +The remediated code example can be found [`here`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unprotected-update-current-contract-wasm/unprotected-update-current-contract-wasm-1/remediated-example). \ No newline at end of file From 90f04c8ce88939f41484cd81a9177e637cfb012f Mon Sep 17 00:00:00 2001 From: Nacho Gutman Date: Tue, 30 Apr 2024 10:21:33 -0300 Subject: [PATCH 02/30] Add unrestricted transfer from vulnerability doc --- .../18-unrestricted-transfer-from.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/docs/vulnerabilities/18-unrestricted-transfer-from.md diff --git a/docs/docs/vulnerabilities/18-unrestricted-transfer-from.md b/docs/docs/vulnerabilities/18-unrestricted-transfer-from.md new file mode 100644 index 00000000..5eb2b3b8 --- /dev/null +++ b/docs/docs/vulnerabilities/18-unrestricted-transfer-from.md @@ -0,0 +1,68 @@ +# Unrestricted Transfer From + +## Description + +- Vulnerability Category: `Validations and error handling` +- Vulnerability Severity: `High` +- Detectors: [`unrestricted-transfer-from`](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/unrestricted-transfer-from) +- Test Cases: [`unrestricted-transfer-from-1`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unrestricted-transfer-from/unrestricted-transfer-from-1) + +Allowing unrestricted `transfer_from` operations poses a significant vulnerability. When `from` arguments for that function is provided directly by the user, this might enable the withdrawal of funds from any actor with token approval on the contract. This could result in unauthorized transfers and loss of funds. + +## Exploit Scenario + +Consider the following `Soroban` function: + +```rust + pub fn deposit(env: Env, from: Address) -> Result<(), UTFError> { + let mut state: State = Self::get_state(env.clone())?; + state.buyer.require_auth(); + if state.status != Status::Created { + return Err(UTFError::StatusMustBeCreated); + } + let token_client = token::Client::new(&env, &state.token); + token_client.transfer_from( + &env.current_contract_address(), + &from, + &env.current_contract_address(), + &state.amount, + ); + state.status = Status::Locked; + env.storage().instance().set(&STATE, &state); + Ok(()) + } +``` + +The vulnerability in this `deposit` function arises from the use of `from`, an user-defined parameter as an argument in the `from` field of the `transfer_from` function. Alice can approve a contract to spend their tokens, then Bob can call that contract, use that allowance to send as themselves Alice's tokens. + +The vulnerable code example can be found [`here`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unrestricted-transfer-from/unrestricted-transfer-from-1/vulnerable-example). + +## Remediation + +Avoid using user-defined arguments as `from` parameter in `transfer_from`. Instead, use `state.buyer` as shown in the following example. + +```rust + pub fn deposit(env: Env) -> Result<(), UTFError> { + let mut state: State = Self::get_state(env.clone())?; + state.buyer.require_auth(); + if state.status != Status::Created { + return Err(UTFError::StatusMustBeCreated); + } + let token_client = token::Client::new(&env, &state.token); + token_client.transfer_from( + &env.current_contract_address(), + &state.buyer, + &env.current_contract_address(), + &state.amount, + ); + state.status = Status::Locked; + env.storage().instance().set(&STATE, &state); + Ok(()) + } +``` + +The remediated code example can be found [`here`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unrestricted-transfer-from/unrestricted-transfer-from-1/remediated-example). + +## References + +- [Slither: Arbitrary from in transferFrom](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom) \ No newline at end of file From 4163e9393ef77e484891c4451e2e50bc10b2580c Mon Sep 17 00:00:00 2001 From: user Date: Mon, 6 May 2024 09:56:02 -0300 Subject: [PATCH 03/30] Add incorrect exponentiation detector --- detectors/incorrect-exponentiation/Cargo.toml | 16 ++++ detectors/incorrect-exponentiation/src/lib.rs | 81 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 detectors/incorrect-exponentiation/Cargo.toml create mode 100644 detectors/incorrect-exponentiation/src/lib.rs diff --git a/detectors/incorrect-exponentiation/Cargo.toml b/detectors/incorrect-exponentiation/Cargo.toml new file mode 100644 index 00000000..012d8d90 --- /dev/null +++ b/detectors/incorrect-exponentiation/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "incorrect-exponentiation" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +scout-audit-clippy-utils = { workspace = true } +dylint_linting = { workspace = true } +if_chain = { workspace = true } + + +[package.metadata.rust-analyzer] +rustc_private = true diff --git a/detectors/incorrect-exponentiation/src/lib.rs b/detectors/incorrect-exponentiation/src/lib.rs new file mode 100644 index 00000000..dbd2c5ae --- /dev/null +++ b/detectors/incorrect-exponentiation/src/lib.rs @@ -0,0 +1,81 @@ +#![feature(rustc_private)] +#![warn(unused_extern_crates)] + +extern crate rustc_hir; +extern crate rustc_span; + +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::Visitor; +use rustc_hir::intravisit::{walk_expr, FnKind}; +use rustc_hir::{Body, FnDecl}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_span::Span; +use scout_audit_clippy_utils::diagnostics::span_lint_and_help; + +const LINT_MESSAGE: &str = "'^' It is not an exponential operator. It is a bitwise XOR."; +const LINT_HELP: &str = "If you want to use XOR, use bitxor(). If you want to raise a number use .checked_pow() or .pow() "; + +dylint_linting::declare_late_lint! { + pub INCORRECT_EXPONENTIATION, + Warn, + LINT_MESSAGE, + { + name: "Incorrect Exponentiation", + long_message: LINT_MESSAGE, + severity: "Critical", + help: "https://coinfabrik.github.io/scout/docs/vulnerabilities/incorrect-exponentiation", + vulnerability_class: "Arithmetic", + } + +} + +impl<'tcx> LateLintPass<'tcx> for IncorrectExponentiation { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: LocalDefId, + ) { + struct IncorrectExponentiationStorage { + span: Vec, + incorrect_exponentiation: bool, + } + + impl<'tcx> Visitor<'tcx> for IncorrectExponentiationStorage { + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(op, _, _) = &expr.kind { + if op.node == rustc_hir::BinOpKind::BitXor { + self.incorrect_exponentiation = true; + self.span.push(expr.span); + } + } + + walk_expr(self, expr); + } + } + + let mut expon_storage = IncorrectExponentiationStorage { + span: Vec::new(), + incorrect_exponentiation: false, + }; + + walk_expr(&mut expon_storage, body.value); + + if expon_storage.incorrect_exponentiation { + for span in expon_storage.span.iter() { + span_lint_and_help( + cx, + INCORRECT_EXPONENTIATION, + *span, + LINT_MESSAGE, + None, + LINT_HELP, + ); + } + } + } +} From 9f2024ce4abbf60e52c3a941b06552682530674c Mon Sep 17 00:00:00 2001 From: user Date: Mon, 6 May 2024 10:53:23 -0300 Subject: [PATCH 04/30] Add incorrect exponentiation test cases --- .../incorrect-exponentiation/Cargo.toml | 22 +++++++ .../remediated-example/Cargo.toml | 16 +++++ .../remediated-example/src/lib.rs | 62 +++++++++++++++++++ .../vulnerable-example/Cargo.toml | 16 +++++ .../vulnerable-example/src/lib.rs | 62 +++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 test-cases/incorrect-exponentiation/Cargo.toml create mode 100644 test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml create mode 100644 test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs create mode 100644 test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml create mode 100644 test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs diff --git a/test-cases/incorrect-exponentiation/Cargo.toml b/test-cases/incorrect-exponentiation/Cargo.toml new file mode 100644 index 00000000..3db3ca7b --- /dev/null +++ b/test-cases/incorrect-exponentiation/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +exclude = [".cargo", "target"] +members = ["incorrect-exponentiation-*/*"] +resolver = "2" + +[workspace.dependencies] +soroban-sdk = "20.3.2" + +[profile.release] +opt-level = "z" +overflow-checks = true +debug = 0 +strip = "symbols" +debug-assertions = false +panic = "abort" +codegen-units = 1 +lto = true + +# For more information about this profile see https://soroban.stellar.org/docs/basic-tutorials/logging#cargotoml-profile +[profile.release-with-logs] +inherits = "release" +debug-assertions = true diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml new file mode 100644 index 00000000..ee9f0b0a --- /dev/null +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "incorrect-exponentiation-remediated-1" +version = "0.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } + +[features] +testutils = ["soroban-sdk/testutils"] diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs new file mode 100644 index 00000000..c1554c4d --- /dev/null +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -0,0 +1,62 @@ +#![no_std] + +use soroban_sdk::{contract, contractimpl, contracttype, Env}; + +#[contracttype] +#[derive(Clone)] +enum DataKey { + Data, +} + +#[contract] +pub struct IncorrectExponentiation; + +#[contractimpl] +impl IncorrectExponentiation { + + pub fn init(e: Env){ + e.storage() + .instance() + .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); + } + + pub fn set_data(e: Env, new_data: u128) { + e.storage() + .instance() + .set::(&DataKey::Data, &new_data); + } + + pub fn exp_data_3(e: Env) -> u128 { + let data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + + return data.pow(3); + } + +} + +#[cfg(test)] +mod tests { + use soroban_sdk::{testutils, Address, Env}; + + use crate::{IncorrectExponentiation, IncorrectExponentiationClient}; + + #[test] + fn simple_test() { + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let user =
::generate(&env); + + + client.init(); + client.set_data(&10_u128); + + assert_eq!(client.exp_data_3(), 1000); +} +} + + diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml new file mode 100644 index 00000000..0300c6ee --- /dev/null +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "incorrect-exponentiation-vulnerable-1" +version = "0.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } + +[features] +testutils = ["soroban-sdk/testutils"] diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs new file mode 100644 index 00000000..c2259eff --- /dev/null +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -0,0 +1,62 @@ +#![no_std] + +use soroban_sdk::{contract, contractimpl, contracttype, Env}; + +#[contracttype] +#[derive(Clone)] +enum DataKey { + Data, +} + +#[contract] +pub struct IncorrectExponentiation; + +#[contractimpl] +impl IncorrectExponentiation { + + pub fn init(e: Env){ + e.storage() + .instance() + .set::(&DataKey::Data, &((255_u128^2) - 1)); + } + + pub fn set_data(e: Env, new_data: u128) { + e.storage() + .instance() + .set::(&DataKey::Data, &new_data); + } + + pub fn exp_data_3(e: Env) -> u128 { + let mut data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + + data = data ^ 3; + return data; + } + +} + +#[cfg(test)] +mod tests { + use soroban_sdk::{testutils, Address, Env}; + + use crate::{IncorrectExponentiation, IncorrectExponentiationClient}; + + #[test] + fn simple_test() { + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let user =
::generate(&env); + + + client.init(); + client.set_data(&10_u128); + + //EXPECTED TO FAIL TO SHOW THAT ^ IS NOT .pow() + assert_eq!(client.exp_data_3(), 1000); +} +} From a086797aadcdef745eb5cc43544e40b5464329a0 Mon Sep 17 00:00:00 2001 From: Nacho Gutman Date: Mon, 6 May 2024 11:31:57 -0300 Subject: [PATCH 05/30] Add incorrect exponentiation vulnerability documentation --- .../21-incorrect-exponentiation.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/docs/vulnerabilities/21-incorrect-exponentiation.md diff --git a/docs/docs/vulnerabilities/21-incorrect-exponentiation.md b/docs/docs/vulnerabilities/21-incorrect-exponentiation.md new file mode 100644 index 00000000..4e9ef330 --- /dev/null +++ b/docs/docs/vulnerabilities/21-incorrect-exponentiation.md @@ -0,0 +1,51 @@ +# Incorrect Exponentiation + +## Description + +- Vulnerability Category: `Arithmetic` +- Vulnerability Severity: `Critical` +- Detectors: [`incorrect-exponentiation`](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/incorrect-exponentiation) +- Test Cases: [`incorrect-exponentiation-1`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/incorrect-exponentiation/incorrect-exponentiation-1) + + +The operator `^` is not an exponential operator, it is a bitwise XOR. Make sure to use `pow()` instead for exponentiation. In case of performing a XOR operation, use `.bitxor()` for clarity. + +## Exploit Scenario + +In the following example, the `^` operand is being used for exponentiation. But in Rust, `^` is the operand for an XOR operation. If misused, this could lead to unexpected behaviour in our contract. + +```rust + pub fn exp_data_3(e: Env) -> u128 { + let mut data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + + data = data ^ 3; + return data; + } +``` + +The vulnerable code example can be found [`here`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example). + +## Remediation + +A possible solution is to use the method `pow()`. But, if a XOR operation is wanted, `.bitxor()` method is recommended. + +```rust + pub fn exp_data_3(e: Env) -> u128 { + let data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + + return data.pow(3); + } +``` + +The remediated code example can be found [`here`](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example). + +## References + +- https://doc.rust-lang.org/std/ops/trait.BitXor.html + From b9369b325899606e9eb381725a0546c4513a07d6 Mon Sep 17 00:00:00 2001 From: tomasavola Date: Mon, 6 May 2024 11:43:34 -0300 Subject: [PATCH 06/30] Add incorrect-exponentation in the detector's README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1792d107..de78bdaf 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Currently Scout includes the following detectors. | [unrestricted-transfer-from](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/unrestricted-transfer-from) | Avoid passing an user-defined parameter as a `from` field in transfer-from. | [1](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unrestricted-transfer-from/unrestricted-transfer-from-1) | Critical | | [unsafe-map-get](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/unsafe-map-get) | Inappropriate usage of the `get` method for `Map` in soroban | [1](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/unsafe-map-get/unsafe-map-get-1) | Medium | | [zero-or-test-address](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/zero-or-test-address) | Avoid zero or test address assignment to prevent contract control loss. | [1](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/zero-or-test-address/zero-or-test-address-1) | Validations and error handling | +| [incorrect-exponentation](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/incorrect-exponentiation) | Warns against incorrect usage of ´^´. | [1](https://github.com/CoinFabrik/scout-soroban/tree/main/test-cases/incorrect-exponentiation/incorrect-exponentiation-1) | Critical | ## Output formats @@ -133,3 +134,4 @@ Our team has an academic background in computer science and mathematics, with wo ## License Scout is licensed and distributed under a MIT license. [Contact us](https://www.coinfabrik.com/) if you're looking for an exception to the terms. + From ff67856deb6a9ce3e07347dcdaa261ad4617a1e0 Mon Sep 17 00:00:00 2001 From: Nacho Gutman Date: Mon, 6 May 2024 11:47:09 -0300 Subject: [PATCH 07/30] Add incorrect exponentiation to vulnerabilities readme --- docs/docs/vulnerabilities/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/vulnerabilities/README.md b/docs/docs/vulnerabilities/README.md index 06feecc3..be0fe933 100644 --- a/docs/docs/vulnerabilities/README.md +++ b/docs/docs/vulnerabilities/README.md @@ -257,3 +257,9 @@ Assigning a test address can also have similar implications, including the loss This vulnerability falls under the [Validations and error handling](#vulnerability-categories) category and has a Medium severity. + +### Incorrect Exponentiation + +It's common to use `^` for exponentiation. However in Rust, `^` is the XOR operator. If the `^` operator is used, it could lead to unexpected behavior in the contract. It's recommended to use the method `pow()` for exponentiation or `.bitxor()` for XOR operations. + +This vulnerability falls under the [Arithmetic](#vulnerability-categories) category and has a Critical severity. \ No newline at end of file From 931b6147cd04b151ec74f7f954172d6788fa155b Mon Sep 17 00:00:00 2001 From: tomasavola Date: Mon, 6 May 2024 11:50:14 -0300 Subject: [PATCH 08/30] Add 21-incorrect-exponentiation.md documentation --- .../detectors/21-incorrect-exponentiation.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/docs/detectors/21-incorrect-exponentiation.md diff --git a/docs/docs/detectors/21-incorrect-exponentiation.md b/docs/docs/detectors/21-incorrect-exponentiation.md new file mode 100644 index 00000000..b3b715bf --- /dev/null +++ b/docs/docs/detectors/21-incorrect-exponentiation.md @@ -0,0 +1,43 @@ +# Zero or test address + +### What it does +Checks whether the zero address is being inputed to a function without validation. + +### Why is this bad? +Because the private key for the zero address is known, anyone could take ownership of the contract. + +### Example + +```rust +pub fn set(e: Env, admin: Address, data: i32) -> Result<(), Error> { + if !ZeroAddressContract::ensure_is_admin(&e, admin)? { + return Err(Error::NotAdmin); + } + e.storage().persistent().set(&DataKey::Data, &data); + Ok(()) +} +``` + + +Use instead: +```rust +pub fn set(e: Env, admin: Address, data: i32) -> Result<(), Error> { + if admin + == Address::from_string(&String::from_bytes( + &e, + b"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", + )) + { + return Err(Error::InvalidNewAdmin); + } + if !ZeroAddressContract::ensure_is_admin(&e, admin)? { + return Err(Error::NotAdmin); + } + e.storage().persistent().set(&DataKey::Data, &data); + Ok(()) +} +``` + +### Implementation + +The detector's implementation can be found at [this link](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/zero-or-test-address). From 566eca503d9daca14f6bdd48ebd0ab1107ee5854 Mon Sep 17 00:00:00 2001 From: nachogutman <108377975+nachogutman@users.noreply.github.com> Date: Mon, 6 May 2024 12:25:45 -0300 Subject: [PATCH 09/30] Correct remediated example for clippy --- .../incorrect-exponentiation-1/remediated-example/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index c1554c4d..06dca809 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -32,7 +32,7 @@ impl IncorrectExponentiation { .get::(&DataKey::Data) .expect("Data not found"); - return data.pow(3); + data.pow(3) } } @@ -49,7 +49,7 @@ mod tests { let contract_id = env.register_contract(None, IncorrectExponentiation); let client = IncorrectExponentiationClient::new(&env, &contract_id); env.mock_all_auths(); - let user =
::generate(&env); + let _user =
::generate(&env); client.init(); From 3dfef01b52806ad10588350959f0a5f19bfd39c8 Mon Sep 17 00:00:00 2001 From: nachogutman <108377975+nachogutman@users.noreply.github.com> Date: Mon, 6 May 2024 12:26:26 -0300 Subject: [PATCH 10/30] Correct vulnerable example for clippy --- .../incorrect-exponentiation-1/vulnerable-example/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index c2259eff..9aa96a32 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -33,7 +33,7 @@ impl IncorrectExponentiation { .expect("Data not found"); data = data ^ 3; - return data; + data } } @@ -50,7 +50,7 @@ mod tests { let contract_id = env.register_contract(None, IncorrectExponentiation); let client = IncorrectExponentiationClient::new(&env, &contract_id); env.mock_all_auths(); - let user =
::generate(&env); + let _user =
::generate(&env); client.init(); From 804b31debb5225f9fc3784fdf003640ae786bdaf Mon Sep 17 00:00:00 2001 From: nachogutman <108377975+nachogutman@users.noreply.github.com> Date: Tue, 7 May 2024 09:42:20 -0300 Subject: [PATCH 11/30] Update vulnerable test --- .../incorrect-exponentiation-1/vulnerable-example/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index 9aa96a32..e4df88fb 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -56,7 +56,6 @@ mod tests { client.init(); client.set_data(&10_u128); - //EXPECTED TO FAIL TO SHOW THAT ^ IS NOT .pow() - assert_eq!(client.exp_data_3(), 1000); + assert_eq!(client.exp_data_3(), 9); } } From ebf9d990a3fbe1212bab548294425737278e604d Mon Sep 17 00:00:00 2001 From: tomasavola Date: Tue, 7 May 2024 10:24:18 -0300 Subject: [PATCH 12/30] Add incorrect-exponentiation documentation --- .../detectors/21-incorrect-exponentiation.md | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/docs/docs/detectors/21-incorrect-exponentiation.md b/docs/docs/detectors/21-incorrect-exponentiation.md index b3b715bf..3186fd9a 100644 --- a/docs/docs/detectors/21-incorrect-exponentiation.md +++ b/docs/docs/detectors/21-incorrect-exponentiation.md @@ -1,43 +1,41 @@ -# Zero or test address +# Incorrect Exponentiation ### What it does -Checks whether the zero address is being inputed to a function without validation. + +Warns about `^` being a `bit XOR` operation instead of an exponentiation. ### Why is this bad? -Because the private key for the zero address is known, anyone could take ownership of the contract. + +It can introduce unexpected behaviour in the smart contract. + +#### More info + +- https://doc.rust-lang.org/std/ops/trait.BitXor.html#tymethod.bitxor ### Example ```rust -pub fn set(e: Env, admin: Address, data: i32) -> Result<(), Error> { - if !ZeroAddressContract::ensure_is_admin(&e, admin)? { - return Err(Error::NotAdmin); + pub fn exp_data_3(e: Env) -> u128 { + let mut data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + data = data ^ 3; + return data; } - e.storage().persistent().set(&DataKey::Data, &data); - Ok(()) -} ``` - - Use instead: + ```rust -pub fn set(e: Env, admin: Address, data: i32) -> Result<(), Error> { - if admin - == Address::from_string(&String::from_bytes( - &e, - b"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", - )) - { - return Err(Error::InvalidNewAdmin); - } - if !ZeroAddressContract::ensure_is_admin(&e, admin)? { - return Err(Error::NotAdmin); + pub fn exp_data_3(e: Env) -> u128 { + let data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + return data.pow(3); } - e.storage().persistent().set(&DataKey::Data, &data); - Ok(()) -} ``` ### Implementation -The detector's implementation can be found at [this link](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/zero-or-test-address). +The detector's implementation can be found at [this link](https://github.com/CoinFabrik/scout-soroban/tree/main/detectors/incorrect-exponentiation). From 1346ba52a27ca35892434557f9d08dec9329d788 Mon Sep 17 00:00:00 2001 From: nachogutman <108377975+nachogutman@users.noreply.github.com> Date: Tue, 7 May 2024 11:30:13 -0300 Subject: [PATCH 13/30] Correct vulnerable testcase for clippy to ignore data = data ^ 3 --- .../incorrect-exponentiation-1/vulnerable-example/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index e4df88fb..c9af8a1c 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![allow(clippy::assign_op_pattern)] use soroban_sdk::{contract, contractimpl, contracttype, Env}; From ee79f18073bcb04f785c7f9e828ca67d8971b369 Mon Sep 17 00:00:00 2001 From: nachogutman <108377975+nachogutman@users.noreply.github.com> Date: Tue, 7 May 2024 11:55:31 -0300 Subject: [PATCH 14/30] Try to fix workflow issue --- .github/workflows/test-detectors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-detectors.yml b/.github/workflows/test-detectors.yml index e14c388f..110d2bb8 100644 --- a/.github/workflows/test-detectors.yml +++ b/.github/workflows/test-detectors.yml @@ -124,6 +124,7 @@ jobs: run: | rustup toolchain install nightly-2023-12-16-x86_64-apple-darwin rustup component add rust-src --toolchain nightly-2023-12-16-x86_64-apple-darwin + rustup component add rust-src --toolchain nightly-2023-12-16-x86_64-unknown-linux-gnu python scripts/run-tests.py --detector=${{ matrix.detector }} comment-on-pr: From e2c447f4496c5f81565140888a90de2454c40741 Mon Sep 17 00:00:00 2001 From: Jose Garcia Crosta Date: Tue, 7 May 2024 15:16:10 -0300 Subject: [PATCH 15/30] Fix CI --- .github/workflows/test-detectors.yml | 18 +++++++----------- scripts/run-fmt.py | 4 ---- scripts/utils.py | 2 +- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test-detectors.yml b/.github/workflows/test-detectors.yml index 110d2bb8..906e0261 100644 --- a/.github/workflows/test-detectors.yml +++ b/.github/workflows/test-detectors.yml @@ -57,8 +57,10 @@ jobs: key: ${{ runner.os }}-tests-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-tests- - - name: Install Rust nightly - run: rustup install nightly-2023-12-16 + - name: Install Rust nightly and add rust-src + run: | + rustup install nightly-2023-12-16 + rustup component add rust-src --toolchain nightly-2023-12-16 - name: Install dylint, dylint-link and cargo-scout-audit run: cargo +nightly-2023-12-16 install cargo-dylint dylint-link cargo-scout-audit @@ -121,11 +123,7 @@ jobs: restore-keys: ${{ runner.os }}-tests- - name: Run unit and integration tests - run: | - rustup toolchain install nightly-2023-12-16-x86_64-apple-darwin - rustup component add rust-src --toolchain nightly-2023-12-16-x86_64-apple-darwin - rustup component add rust-src --toolchain nightly-2023-12-16-x86_64-unknown-linux-gnu - python scripts/run-tests.py --detector=${{ matrix.detector }} + run: python scripts/run-tests.py --detector=${{ matrix.detector }} comment-on-pr: name: Comment on PR @@ -140,13 +138,11 @@ jobs: id: ubuntu_status working-directory: build-status-ubuntu-latest run: echo "status=$(cat status-ubuntu-latest.txt)" >> $GITHUB_OUTPUT - continue-on-error: true - name: Read macOS build status id: macos_status - working-directory: build-status-macos-latest - run: echo "status=$(cat status-macos-latest.txt)" >> $GITHUB_OUTPUT - continue-on-error: true + working-directory: build-status-macos-13 + run: echo "status=$(cat status-macos-13.txt)" >> $GITHUB_OUTPUT - name: Find comment id: find_comment diff --git a/scripts/run-fmt.py b/scripts/run-fmt.py index 7685aaaf..824812bd 100644 --- a/scripts/run-fmt.py +++ b/scripts/run-fmt.py @@ -21,10 +21,6 @@ def run_fmt(directories): for root, _, _ in os.walk(directory): if is_rust_project(root): start_time = time.time() - returncode, _, stderr = run_subprocess( - ["cargo", "fmt"], - cwd=root, - ) returncode, _, stderr = run_subprocess( ["cargo", "fmt", "--check"], cwd=root, diff --git a/scripts/utils.py b/scripts/utils.py index b9d0e8cf..ffa7008d 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -78,7 +78,7 @@ def print_results(returncode, error_message, check_type, root, elapsed_time): ) if returncode != 0: print(f"\n{RED}{check_type.capitalize()} {issue_type} found in: {root}{ENDC}\n") - if not error_message is None: + if error_message is not None: for line in error_message.strip().split("\n"): print(f"| {line}") print("\n") From 119b5ed671ad9537610f70a014980fc0bf629142 Mon Sep 17 00:00:00 2001 From: Jose Garcia Crosta Date: Tue, 7 May 2024 15:25:12 -0300 Subject: [PATCH 16/30] Delete rustfmt unused config file and fix rust-fmt ci --- rustfmt.toml | 3 -- .../incorrect-exponentiation/Cargo.toml | 15 ++++--- .../remediated-example/Cargo.toml | 2 +- .../remediated-example/src/lib.rs | 42 +++++++++---------- .../vulnerable-example/Cargo.toml | 2 +- .../vulnerable-example/src/lib.rs | 42 +++++++++---------- 6 files changed, 48 insertions(+), 58 deletions(-) delete mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index cc30efb4..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -group_imports = "StdExternalCrate" -ignore = ["scout-audit-clippy-utils"] -unstable_features = true diff --git a/test-cases/incorrect-exponentiation/Cargo.toml b/test-cases/incorrect-exponentiation/Cargo.toml index 3db3ca7b..fc211bbd 100644 --- a/test-cases/incorrect-exponentiation/Cargo.toml +++ b/test-cases/incorrect-exponentiation/Cargo.toml @@ -4,19 +4,18 @@ members = ["incorrect-exponentiation-*/*"] resolver = "2" [workspace.dependencies] -soroban-sdk = "20.3.2" +soroban-sdk = { version = "20.3.2" } [profile.release] -opt-level = "z" -overflow-checks = true +codegen-units = 1 debug = 0 -strip = "symbols" debug-assertions = false -panic = "abort" -codegen-units = 1 lto = true +opt-level = "z" +overflow-checks = true +panic = "abort" +strip = "symbols" -# For more information about this profile see https://soroban.stellar.org/docs/basic-tutorials/logging#cargotoml-profile [profile.release-with-logs] -inherits = "release" debug-assertions = true +inherits = "release" diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml index ee9f0b0a..172c22cd 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "incorrect-exponentiation-remediated-1" -version = "0.0.0" +version = "0.1.0" edition = "2021" [lib] diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index 06dca809..45991cb9 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -13,28 +13,27 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - - pub fn init(e: Env){ + pub fn init(e: Env) { e.storage() - .instance() - .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); + .instance() + .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); } pub fn set_data(e: Env, new_data: u128) { e.storage() - .instance() - .set::(&DataKey::Data, &new_data); + .instance() + .set::(&DataKey::Data, &new_data); } pub fn exp_data_3(e: Env) -> u128 { - let data = e.storage() - .instance() - .get::(&DataKey::Data) - .expect("Data not found"); + let data = e + .storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); data.pow(3) } - } #[cfg(test)] @@ -45,18 +44,15 @@ mod tests { #[test] fn simple_test() { - let env = Env::default(); - let contract_id = env.register_contract(None, IncorrectExponentiation); - let client = IncorrectExponentiationClient::new(&env, &contract_id); - env.mock_all_auths(); - let _user =
::generate(&env); + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let _user =
::generate(&env); + client.init(); + client.set_data(&10_u128); - client.init(); - client.set_data(&10_u128); - - assert_eq!(client.exp_data_3(), 1000); -} + assert_eq!(client.exp_data_3(), 1000); + } } - - diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml index 0300c6ee..9c2ff3ae 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "incorrect-exponentiation-vulnerable-1" -version = "0.0.0" +version = "0.1.0" edition = "2021" [lib] diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index c9af8a1c..94e7f8a3 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -14,29 +14,28 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - - pub fn init(e: Env){ + pub fn init(e: Env) { e.storage() - .instance() - .set::(&DataKey::Data, &((255_u128^2) - 1)); + .instance() + .set::(&DataKey::Data, &((255_u128 ^ 2) - 1)); } pub fn set_data(e: Env, new_data: u128) { e.storage() - .instance() - .set::(&DataKey::Data, &new_data); + .instance() + .set::(&DataKey::Data, &new_data); } pub fn exp_data_3(e: Env) -> u128 { - let mut data = e.storage() - .instance() - .get::(&DataKey::Data) - .expect("Data not found"); - + let mut data = e + .storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + data = data ^ 3; data } - } #[cfg(test)] @@ -47,16 +46,15 @@ mod tests { #[test] fn simple_test() { - let env = Env::default(); - let contract_id = env.register_contract(None, IncorrectExponentiation); - let client = IncorrectExponentiationClient::new(&env, &contract_id); - env.mock_all_auths(); - let _user =
::generate(&env); + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let _user =
::generate(&env); + client.init(); + client.set_data(&10_u128); - client.init(); - client.set_data(&10_u128); - - assert_eq!(client.exp_data_3(), 9); -} + assert_eq!(client.exp_data_3(), 9); + } } From 21d4f442166ff9c5403cb08bac79cf711d6ed4f1 Mon Sep 17 00:00:00 2001 From: Jose Garcia Crosta Date: Tue, 7 May 2024 16:28:38 -0300 Subject: [PATCH 17/30] Add manual dispatch on workflows --- .github/workflows/deploy-docs.yml | 1 + .github/workflows/general-rust.yml | 1 + .github/workflows/test-deploy-docs.yml | 1 + .github/workflows/test-detectors.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 38253d87..bed58610 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -6,6 +6,7 @@ on: - main paths: - "docs/**" + workflow_dispatch: jobs: deploy: diff --git a/.github/workflows/general-rust.yml b/.github/workflows/general-rust.yml index d8de7de7..c8b0e93d 100644 --- a/.github/workflows/general-rust.yml +++ b/.github/workflows/general-rust.yml @@ -8,6 +8,7 @@ on: - "scripts/**" - "!detectors/**/*.md" - "!test-cases/**/*.md" + workflow_dispatch: env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/test-deploy-docs.yml b/.github/workflows/test-deploy-docs.yml index 80afe1ad..7fe2e13e 100644 --- a/.github/workflows/test-deploy-docs.yml +++ b/.github/workflows/test-deploy-docs.yml @@ -6,6 +6,7 @@ on: - main paths: - "docs/**" + workflow_dispatch: jobs: test-deploy: diff --git a/.github/workflows/test-detectors.yml b/.github/workflows/test-detectors.yml index 906e0261..eb8f3085 100644 --- a/.github/workflows/test-detectors.yml +++ b/.github/workflows/test-detectors.yml @@ -8,6 +8,7 @@ on: - "scripts/**" - "!detectors/**/*.md" - "!test-cases/**/*.md" + workflow_dispatch: env: CARGO_TERM_COLOR: always From a74674008d894892aab24439c6ecd93ec4bb8561 Mon Sep 17 00:00:00 2001 From: tomasavola Date: Thu, 9 May 2024 11:28:15 -0300 Subject: [PATCH 18/30] Update vulnerability --- .../21-incorrect-exponentiation.md | 6 +-- .../remediated-example/src/lib.rs | 40 ++++++++-------- .../vulnerable-example/src/lib.rs | 46 ++++++++----------- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/docs/docs/vulnerabilities/21-incorrect-exponentiation.md b/docs/docs/vulnerabilities/21-incorrect-exponentiation.md index 4e9ef330..44946f76 100644 --- a/docs/docs/vulnerabilities/21-incorrect-exponentiation.md +++ b/docs/docs/vulnerabilities/21-incorrect-exponentiation.md @@ -21,8 +21,8 @@ In the following example, the `^` operand is being used for exponentiation. But .get::(&DataKey::Data) .expect("Data not found"); - data = data ^ 3; - return data; + data ^= 3; + data } ``` @@ -39,7 +39,7 @@ A possible solution is to use the method `pow()`. But, if a XOR operation is wan .get::(&DataKey::Data) .expect("Data not found"); - return data.pow(3); + data.pow(3) } ``` diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index 45991cb9..53d48a5b 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -13,27 +13,22 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - pub fn init(e: Env) { - e.storage() - .instance() - .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); - } pub fn set_data(e: Env, new_data: u128) { e.storage() - .instance() - .set::(&DataKey::Data, &new_data); + .instance() + .set::(&DataKey::Data, &new_data); } pub fn exp_data_3(e: Env) -> u128 { - let data = e - .storage() - .instance() - .get::(&DataKey::Data) - .expect("Data not found"); + let data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); data.pow(3) } + } #[cfg(test)] @@ -44,15 +39,18 @@ mod tests { #[test] fn simple_test() { - let env = Env::default(); - let contract_id = env.register_contract(None, IncorrectExponentiation); - let client = IncorrectExponentiationClient::new(&env, &contract_id); - env.mock_all_auths(); - let _user =
::generate(&env); + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let _user =
::generate(&env); - client.init(); - client.set_data(&10_u128); - assert_eq!(client.exp_data_3(), 1000); - } + + client.set_data(&10_u128); + + assert_eq!(client.exp_data_3(), 1000); +} } + + diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index 94e7f8a3..b6c746c9 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![allow(clippy::assign_op_pattern)] use soroban_sdk::{contract, contractimpl, contracttype, Env}; @@ -14,28 +13,24 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - pub fn init(e: Env) { - e.storage() - .instance() - .set::(&DataKey::Data, &((255_u128 ^ 2) - 1)); - } pub fn set_data(e: Env, new_data: u128) { e.storage() - .instance() - .set::(&DataKey::Data, &new_data); + .instance() + .set::(&DataKey::Data, &new_data); } + pub fn exp_data_3(e: Env) -> u128 { - let mut data = e - .storage() - .instance() - .get::(&DataKey::Data) - .expect("Data not found"); - - data = data ^ 3; + let mut data = e.storage() + .instance() + .get::(&DataKey::Data) + .expect("Data not found"); + + data ^= 3; data } + } #[cfg(test)] @@ -46,15 +41,14 @@ mod tests { #[test] fn simple_test() { - let env = Env::default(); - let contract_id = env.register_contract(None, IncorrectExponentiation); - let client = IncorrectExponentiationClient::new(&env, &contract_id); - env.mock_all_auths(); - let _user =
::generate(&env); - - client.init(); - client.set_data(&10_u128); - - assert_eq!(client.exp_data_3(), 9); - } + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let _user =
::generate(&env); + + client.set_data(&10_u128); + + assert_eq!(client.exp_data_3(), 9); +} } From 10e840205c1153e60ba15bc65adcb7534dbc9071 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 9 May 2024 12:54:21 -0300 Subject: [PATCH 19/30] Correct test cases and detector documentation --- .../detectors/21-incorrect-exponentiation.md | 21 +++++------ .../remediated-example/src/lib.rs | 33 +++++++++-------- .../vulnerable-example/src/lib.rs | 36 +++++++++---------- 3 files changed, 41 insertions(+), 49 deletions(-) diff --git a/docs/docs/detectors/21-incorrect-exponentiation.md b/docs/docs/detectors/21-incorrect-exponentiation.md index 3186fd9a..b2584959 100644 --- a/docs/docs/detectors/21-incorrect-exponentiation.md +++ b/docs/docs/detectors/21-incorrect-exponentiation.md @@ -15,24 +15,19 @@ It can introduce unexpected behaviour in the smart contract. ### Example ```rust - pub fn exp_data_3(e: Env) -> u128 { - let mut data = e.storage() - .instance() - .get::(&DataKey::Data) - .expect("Data not found"); - data = data ^ 3; - return data; + pub fn init(e: Env){ + e.storage() + .instance() + .set::(&DataKey::Data, &((255_u128 ^ 2) - 1)); } ``` Use instead: ```rust - pub fn exp_data_3(e: Env) -> u128 { - let data = e.storage() - .instance() - .get::(&DataKey::Data) - .expect("Data not found"); - return data.pow(3); + pub fn init(e: Env) { + e.storage() + .instance() + .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); } ``` diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index 45991cb9..22c8ab10 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -1,6 +1,13 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, Env}; +use soroban_sdk::{contract, contractimpl, contracttype, contracterror, Env}; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum IEError { + CouldntRetrieveData = 1, +} #[contracttype] #[derive(Clone)] @@ -19,20 +26,14 @@ impl IncorrectExponentiation { .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); } - pub fn set_data(e: Env, new_data: u128) { - e.storage() - .instance() - .set::(&DataKey::Data, &new_data); - } - - pub fn exp_data_3(e: Env) -> u128 { - let data = e - .storage() + pub fn get_data(e: Env) -> Result { + let data = e.storage() .instance() - .get::(&DataKey::Data) - .expect("Data not found"); - - data.pow(3) + .get(&DataKey::Data); + match data { + Some(x) => Ok(x), + None => return Err(IEError::CouldntRetrieveData) + } } } @@ -51,8 +52,6 @@ mod tests { let _user =
::generate(&env); client.init(); - client.set_data(&10_u128); - - assert_eq!(client.exp_data_3(), 1000); + assert_eq!(client.get_data(), 65024); } } diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index 94e7f8a3..13d3af8d 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -1,7 +1,14 @@ #![no_std] #![allow(clippy::assign_op_pattern)] -use soroban_sdk::{contract, contractimpl, contracttype, Env}; +use soroban_sdk::{contract, contractimpl, contracttype, contracterror, Env}; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum IEError { + CouldntRetrieveData = 1, +} #[contracttype] #[derive(Clone)] @@ -14,27 +21,20 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - pub fn init(e: Env) { + pub fn init(e: Env){ e.storage() .instance() .set::(&DataKey::Data, &((255_u128 ^ 2) - 1)); } - pub fn set_data(e: Env, new_data: u128) { - e.storage() - .instance() - .set::(&DataKey::Data, &new_data); - } - - pub fn exp_data_3(e: Env) -> u128 { - let mut data = e - .storage() + pub fn get_data(e: Env) -> Result { + let data = e.storage() .instance() - .get::(&DataKey::Data) - .expect("Data not found"); - - data = data ^ 3; - data + .get(&DataKey::Data); + match data { + Some(x) => Ok(x), + None => return Err(IEError::CouldntRetrieveData) + } } } @@ -53,8 +53,6 @@ mod tests { let _user =
::generate(&env); client.init(); - client.set_data(&10_u128); - - assert_eq!(client.exp_data_3(), 9); + assert_ne!(client.get_data(), 65024); } } From 21afdcbc48bb13e2454e5af0a83a8abfc16c595a Mon Sep 17 00:00:00 2001 From: arturoBeccar Date: Thu, 9 May 2024 13:51:36 -0300 Subject: [PATCH 20/30] Use pow() --- .../incorrect-exponentiation-1/remediated-example/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index 437923fe..947ace1a 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -23,7 +23,7 @@ impl IncorrectExponentiation { pub fn init(e: Env){ e.storage() .instance() - .set::(&DataKey::Data, &((255_u128 ^ 2) - 1)); + .set::(&DataKey::Data, &((255_u128.pow(2)) - 1)); } pub fn get_data(e: Env) -> Result { From 9e774b6a219259c061e480accb8e25e70701b6d5 Mon Sep 17 00:00:00 2001 From: arturoBeccar Date: Thu, 9 May 2024 13:53:12 -0300 Subject: [PATCH 21/30] Remove parenthesis --- .../incorrect-exponentiation-1/remediated-example/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index 947ace1a..a8d82fb7 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -23,7 +23,7 @@ impl IncorrectExponentiation { pub fn init(e: Env){ e.storage() .instance() - .set::(&DataKey::Data, &((255_u128.pow(2)) - 1)); + .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); } pub fn get_data(e: Env) -> Result { @@ -35,7 +35,6 @@ impl IncorrectExponentiation { None => return Err(IEError::CouldntRetrieveData) } } - } #[cfg(test)] From b5817a2e9065118c421c6117abf089b44913db3e Mon Sep 17 00:00:00 2001 From: user Date: Fri, 10 May 2024 09:52:22 -0300 Subject: [PATCH 22/30] Correct format and unneeded return --- .../remediated-example/src/lib.rs | 20 +++++++++---------- .../vulnerable-example/src/lib.rs | 20 +++++++++---------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index a8d82fb7..7b0a713f 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, contracterror, Env}; +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Env}; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] @@ -20,19 +20,17 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - pub fn init(e: Env){ + pub fn init(e: Env) { e.storage() .instance() .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); } pub fn get_data(e: Env) -> Result { - let data = e.storage() - .instance() - .get(&DataKey::Data); + let data = e.storage().instance().get(&DataKey::Data); match data { Some(x) => Ok(x), - None => return Err(IEError::CouldntRetrieveData) + None => Err(IEError::CouldntRetrieveData), } } } @@ -45,11 +43,11 @@ mod tests { #[test] fn simple_test() { - let env = Env::default(); - let contract_id = env.register_contract(None, IncorrectExponentiation); - let client = IncorrectExponentiationClient::new(&env, &contract_id); - env.mock_all_auths(); - let _user =
::generate(&env); + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let _user =
::generate(&env); client.init(); assert_eq!(client.get_data(), 65024); diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs index 3c4e4839..d1fdec44 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/vulnerable-example/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, contracterror, Env}; +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Env}; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] @@ -20,19 +20,17 @@ pub struct IncorrectExponentiation; #[contractimpl] impl IncorrectExponentiation { - pub fn init(e: Env){ + pub fn init(e: Env) { e.storage() .instance() .set::(&DataKey::Data, &((255_u128 ^ 2) - 1)); } pub fn get_data(e: Env) -> Result { - let data = e.storage() - .instance() - .get(&DataKey::Data); + let data = e.storage().instance().get(&DataKey::Data); match data { Some(x) => Ok(x), - None => return Err(IEError::CouldntRetrieveData) + None => Err(IEError::CouldntRetrieveData), } } } @@ -45,11 +43,11 @@ mod tests { #[test] fn simple_test() { - let env = Env::default(); - let contract_id = env.register_contract(None, IncorrectExponentiation); - let client = IncorrectExponentiationClient::new(&env, &contract_id); - env.mock_all_auths(); - let _user =
::generate(&env); + let env = Env::default(); + let contract_id = env.register_contract(None, IncorrectExponentiation); + let client = IncorrectExponentiationClient::new(&env, &contract_id); + env.mock_all_auths(); + let _user =
::generate(&env); client.init(); assert_ne!(client.get_data(), 65024); From d74858ebd1834882cfd14db3a33a8ec3a7012215 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 10 May 2024 10:14:38 -0300 Subject: [PATCH 23/30] correct format --- .../incorrect-exponentiation-1/remediated-example/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs index 7b0a713f..9db61a4b 100644 --- a/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs +++ b/test-cases/incorrect-exponentiation/incorrect-exponentiation-1/remediated-example/src/lib.rs @@ -25,7 +25,7 @@ impl IncorrectExponentiation { .instance() .set::(&DataKey::Data, &(255_u128.pow(2) - 1)); } - + pub fn get_data(e: Env) -> Result { let data = e.storage().instance().get(&DataKey::Data); match data { @@ -53,5 +53,3 @@ mod tests { assert_eq!(client.get_data(), 65024); } } - - From af8ec879afdcae1d1c8c5c36b0590bf528b5ca29 Mon Sep 17 00:00:00 2001 From: matiascabello Date: Mon, 13 May 2024 18:53:26 -0300 Subject: [PATCH 24/30] added scout vs code extension page to docusaurus and made some changes on the readme file --- README.md | 10 +++++++++- docs/docs/intro.md | 8 +++++++- docs/docs/vscode-extension | 16 ++++++++++++++++ docs/static/img/vscode-extension.png | Bin 0 -> 59305 bytes 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 docs/docs/vscode-extension create mode 100644 docs/static/img/vscode-extension.png diff --git a/README.md b/README.md index 1792d107..d9a56413 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,12 @@ Afterwards, install Scout with the following command: cargo install cargo-scout-audit ``` +Finally, install additional Rust components required by Scout. + +```bash +rustup component add rust-src --toolchain nightly-2023-12-16 +``` + To run Scout on your project, navigate to the root directory of your smart contract (where the `Cargo.toml` file is) and execute the following command: ```bash @@ -75,13 +81,15 @@ cargo scout-audit --output-format [html|md] ![Scout HTML report.](/docs/static/img/scout-soroban-html.jpg) -## VS Code extension +## Scout VS Code extension Add Scout to your development workspace with Scout's VS Code extension to run Scout automatically upon saving your file. ![Scout VS Code extension.](/assets/vscode-extension.png) +:warning: To ensure the extension runs properly, make sure that you open the directory containing your smart contract, rather than the entire project. For example, if your smart contracts are located in `myproject/contracts`, and you want to work on the `token` contract while using the Scout VS Code Extension, open `myproject/contracts/token`. +:bulb: Tip: To see the errors highlighted in your code, we recommend installing the [Error Lens Extension](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens). :point_right: Download Scout VS Code from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=CoinFabrik.scout-audit). diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 9116b736..955a6744 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -40,9 +40,15 @@ Afterwards, install Scout with the following command: cargo install cargo-scout-audit ``` +Finally, install additional Rust components required by Scout. + +```bash +rustup component add rust-src --toolchain nightly-2023-12-16 +``` + ### Usage -To run Scout on your project, navigate to its root directory and execute the following command: +To run Scout on your project, navigate to the root directory of your smart contract (where the `Cargo.toml` file is) and execute the following command: ```bash cargo scout-audit diff --git a/docs/docs/vscode-extension b/docs/docs/vscode-extension new file mode 100644 index 00000000..68d79e84 --- /dev/null +++ b/docs/docs/vscode-extension @@ -0,0 +1,16 @@ +--- +sidebar_position: 8 +--- + +# Scout VS Code Extension + +Add Scout to your development workspace with Scout's VS Code extension and run Scout automatically upon saving your file. + +![Scout VS Code extension.](/docs/static/img/vscode-extension.png) + +:warning: To ensure the extension runs properly, make sure that you open the directory containing your smart contract, rather than the entire project. For example, if your smart contracts are located in `myproject/contracts`, and you want to work on the `token` contract while using the Scout VS Code Extension, open `myproject/contracts/token`. + +:bulb: Tip: To see the errors highlighted in your code, we recommend installing the [Error Lens Extension](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens). + +:point_right: Download Scout VS Code from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=CoinFabrik.scout-audit). + diff --git a/docs/static/img/vscode-extension.png b/docs/static/img/vscode-extension.png new file mode 100644 index 0000000000000000000000000000000000000000..844351f1fbf23835b3c2700f2b942f55e0eed779 GIT binary patch literal 59305 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 g$)ibX=8rp&I9hKk9b^ZJ3dv@Xf1yvkX=>Px# literal 0 HcmV?d00001 From 52e7854ed17ee963b62b492f8164669df798c9b5 Mon Sep 17 00:00:00 2001 From: matiascabello Date: Tue, 14 May 2024 11:37:28 -0300 Subject: [PATCH 25/30] fixed vscode documentation file extension --- docs/docs/{vscode-extension => vscode-extension.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/docs/{vscode-extension => vscode-extension.md} (100%) diff --git a/docs/docs/vscode-extension b/docs/docs/vscode-extension.md similarity index 100% rename from docs/docs/vscode-extension rename to docs/docs/vscode-extension.md From e6947d8b08881497576285bda2b0a7f72bce3264 Mon Sep 17 00:00:00 2001 From: matiascabello Date: Tue, 14 May 2024 12:28:47 -0300 Subject: [PATCH 26/30] fix --- docs/docs/vscode-extension.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/vscode-extension.md b/docs/docs/vscode-extension.md index 68d79e84..9de28c8b 100644 --- a/docs/docs/vscode-extension.md +++ b/docs/docs/vscode-extension.md @@ -6,7 +6,7 @@ sidebar_position: 8 Add Scout to your development workspace with Scout's VS Code extension and run Scout automatically upon saving your file. -![Scout VS Code extension.](/docs/static/img/vscode-extension.png) +![Scout VS Code extension.](../docs/static/img/vscode-extension.png) :warning: To ensure the extension runs properly, make sure that you open the directory containing your smart contract, rather than the entire project. For example, if your smart contracts are located in `myproject/contracts`, and you want to work on the `token` contract while using the Scout VS Code Extension, open `myproject/contracts/token`. From d79ee3943f6719ad61d841734ce6946aa76b37b0 Mon Sep 17 00:00:00 2001 From: matiascabello <49765916+matiascabello@users.noreply.github.com> Date: Tue, 14 May 2024 12:40:36 -0300 Subject: [PATCH 27/30] Update vscode-extension.md --- docs/docs/vscode-extension.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/vscode-extension.md b/docs/docs/vscode-extension.md index 9de28c8b..45048547 100644 --- a/docs/docs/vscode-extension.md +++ b/docs/docs/vscode-extension.md @@ -6,7 +6,7 @@ sidebar_position: 8 Add Scout to your development workspace with Scout's VS Code extension and run Scout automatically upon saving your file. -![Scout VS Code extension.](../docs/static/img/vscode-extension.png) +![Scout VS Code extension.](../static/img/vscode-extension.png) :warning: To ensure the extension runs properly, make sure that you open the directory containing your smart contract, rather than the entire project. For example, if your smart contracts are located in `myproject/contracts`, and you want to work on the `token` contract while using the Scout VS Code Extension, open `myproject/contracts/token`. From 3a7738dc655603f93cf497c02977b5ba3d2385b0 Mon Sep 17 00:00:00 2001 From: matiascabello Date: Tue, 14 May 2024 17:54:13 -0300 Subject: [PATCH 28/30] updated installation instructions --- README.md | 26 ++++++++++++++++++++------ docs/docs/intro.md | 28 +++++++++++++++++++++------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 65627da9..bc0e45f7 100644 --- a/README.md +++ b/README.md @@ -14,25 +14,39 @@ Our interest in this project comes from our experience in manual auditing and vu ## Quick Start -For a quick start, make sure that [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) is installed on your computer. Then, install Scout dependencies by running the following command: +Make sure that [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) is installed on your computer. Then, follow these 5 simple steps: + +**1. Install Rust Nightly Toolchain:** ```bash -cargo install cargo-dylint dylint-link +rustup toolchain install nightly-2023-12-16 ``` -Afterwards, install Scout with the following command: +**2. Set Default Nightly Toolchain:** ```bash -cargo install cargo-scout-audit +rustup default nightly-2023-12-16 ``` -Finally, install additional Rust components required by Scout. +**3. Add rust-src Component:** ```bash rustup component add rust-src --toolchain nightly-2023-12-16 ``` -To run Scout on your project, navigate to the root directory of your smart contract (where the `Cargo.toml` file is) and execute the following command: +**4. Install additional tools required by Scout:** + +```bash +cargo install cargo-dylint dylint-link mdbook +``` + +**5. Install Scout Audit:** + +```bash +cargo install cargo-scout-audit +``` + +Finally, to run Scout on your project, navigate to the root directory of your smart contract (where the `Cargo.toml` file is) and execute the following command: ```bash cargo scout-audit diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 955a6744..0cc57eae 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -14,11 +14,11 @@ Scout is an extensible open-source tool intended to assist Stellar's Soroban sma - A list of vulnerabilities, best practices and enhancements, together with associated detectors to identify these issues in your code - Command Line Interface (CLI) -- VSCode Extension +- VS Code Extension ### What you'll need -Make sure that [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) is installed on your computer. For using the VSCode Extension you must be using [VSCode](https://code.visualstudio.com/). +Make sure that [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) is installed on your computer. For using the VSCode Extension you must be using [VS Code](https://code.visualstudio.com/). You should be able to install and run Scout without issues on Mac or Linux. You can also use it in Windows through WSL. @@ -28,24 +28,38 @@ The command line interface is designed to allow you to run Scout on an entire pr ### Installation -FIn order to install the Command Line Interface, first install Scout dependencies by running the following command: +Install Scout in 5 simple steps: + +**1. Install Rust Nightly Toolchain:** ```bash -cargo install cargo-dylint dylint-link +rustup toolchain install nightly-2023-12-16 ``` -Afterwards, install Scout with the following command: +**2. Set Default Nightly Toolchain:** ```bash -cargo install cargo-scout-audit +rustup default nightly-2023-12-16 ``` -Finally, install additional Rust components required by Scout. +**3. Add rust-src Component:** ```bash rustup component add rust-src --toolchain nightly-2023-12-16 ``` +**4. Install additional tools required by Scout:** + +```bash +cargo install cargo-dylint dylint-link mdbook +``` + +**5. Install Scout Audit:** + +```bash +cargo install cargo-scout-audit +``` + ### Usage To run Scout on your project, navigate to the root directory of your smart contract (where the `Cargo.toml` file is) and execute the following command: From a10fc111e326462691099c5a182d392458d27fc7 Mon Sep 17 00:00:00 2001 From: matiascabello Date: Wed, 15 May 2024 10:40:17 -0300 Subject: [PATCH 29/30] added soroban contracts examples documentation --- docs/docs/soroban-examples.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/docs/soroban-examples.md diff --git a/docs/docs/soroban-examples.md b/docs/docs/soroban-examples.md new file mode 100644 index 00000000..41830305 --- /dev/null +++ b/docs/docs/soroban-examples.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 9 +--- + +# Soroban Smart Contracts Examples + +In the context of Scout's development, we engaged developers without Soroban experience to create a series of smart contracts within tight time constraints, encouraging them to introduce errors. + +Following this, a senior auditor from CoinFabrik conducted a security review of these smart contracts, focusing on vulnerabilities, deviations from best practices, and potential improvements. + +The objective was to obtain a set of smart contracts for Scout testing purposes, allowing us to compare the tool's results with the auditor's findings and refine Scout accordingly, such as by adding new detectors or enhancing existing ones. + +Moreover, these smart contracts will serve as educational resources for the Soroban developer community. The security review findings will also help raise awareness about common issues. + +## List of Implemented Smart Contracts + +Follow the links to the smart contract. Each of them includes a README file with an overview, the functions implemented, and a description of each, as well as instructions on how to interact with the contract. + +- [Automatic Market Maker](https://github.com/CoinFabrik/scout-soroban-examples/tree/main/amm) +- [Governance](https://github.com/CoinFabrik/scout-soroban-examples/tree/main/governance) +- [Multi Contract Caller](https://github.com/CoinFabrik/scout-soroban-examples/tree/main/multi-contract-caller) +- [Multisig](https://github.com/CoinFabrik/scout-soroban-examples/tree/main/multisig) +- [Payment Channel](https://github.com/CoinFabrik/scout-soroban-examples/tree/main/payment-channel) +- [Vesting Program](https://github.com/CoinFabrik/scout-soroban-examples/tree/main/vesting) + +## Security Review + +The security review reported 20 issues and 9 enhancements. + +:point_right: Navigate to [this link](https://github.com/CoinFabrik/scout-soroban-examples/blob/main/security-review/README.md) to view the security review. + +:warning: Take into consideration that this is not a full security audit. Use these smart contracts with caution. \ No newline at end of file From ba915662932dda895eef7595fb06df54c2aeff0d Mon Sep 17 00:00:00 2001 From: matiascabello Date: Wed, 15 May 2024 11:24:07 -0300 Subject: [PATCH 30/30] fixed title --- docs/docs/soroban-examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/soroban-examples.md b/docs/docs/soroban-examples.md index 41830305..390795bf 100644 --- a/docs/docs/soroban-examples.md +++ b/docs/docs/soroban-examples.md @@ -2,7 +2,7 @@ sidebar_position: 9 --- -# Soroban Smart Contracts Examples +# Scout Soroban Smart Contracts Examples In the context of Scout's development, we engaged developers without Soroban experience to create a series of smart contracts within tight time constraints, encouraging them to introduce errors.