Skip to content

Commit

Permalink
57 pragma declarations and version control (#64)
Browse files Browse the repository at this point in the history
* Add soroban-version detector with test cases
  • Loading branch information
jgcrosta authored Dec 21, 2023
1 parent 4bf169a commit 3feb33b
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/test-detectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ jobs:
"insufficiently-random-values",
"overflow-check",
"set-contract-storage",
"soroban-version",
"unprotected-update-current-contract-wasm",
"unsafe-expect",
"unsafe-unwrap",
Expand Down
23 changes: 23 additions & 0 deletions detectors/soroban-version/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "soroban-version"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
dylint_linting = { workspace = true }
if_chain = { workspace = true }
semver = "1.0.4"
serde_json = "1.0"
toml = "0.8.8"
ureq = { version = "2.7.1", features = ["json"] }

scout-audit-internal = { workspace = true }

[dev-dependencies]
dylint_testing = { workspace = true }

[package.metadata.rust-analyzer]
rustc_private = true
134 changes: 134 additions & 0 deletions detectors/soroban-version/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#![feature(rustc_private)]

extern crate rustc_ast;
extern crate rustc_hir;
extern crate rustc_span;

use std::{env, fs, io::Error, path::Path};

use rustc_ast::Crate;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use scout_audit_internal::Detector;
use semver::*;

dylint_linting::declare_early_lint! {
/// ### What it does
/// Checks the soroban version of the contract
///
/// ### Why is this bad?
/// Using an outdated version of soroban could lead to security vulnerabilities, bugs, and other issues.
pub CHECK_SOROBAN_VERSION,
Warn,
Detector::SorobanVersion.get_lint_message()
}

impl EarlyLintPass for CheckSorobanVersion {
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
let latest_version = match get_version() {
Ok(version) => version,
Err(e) => {
cx.sess()
.struct_warn(format!("Failed to get the latest Soroban version: {}", e))
.emit();
return;
}
};

let manifest_dir = match env::var("CARGO_MANIFEST_DIR") {
Ok(dir) => dir,
Err(e) => {
cx.sess()
.struct_warn(format!(
"Environment variable CARGO_MANIFEST_DIR not found: {}",
e
))
.emit();
return;
}
};

let cargo_toml_path = Path::new(&manifest_dir).join("Cargo.toml");
let cargo_toml = match fs::read_to_string(cargo_toml_path) {
Ok(content) => content,
Err(e) => {
cx.sess()
.struct_warn(format!("Unable to read Cargo.toml: {}", e))
.emit();
return;
}
};

let toml: toml::Value = match toml::from_str(&cargo_toml) {
Ok(value) => value,
Err(e) => {
cx.sess()
.struct_warn(format!("Error parsing Cargo.toml: {}", e))
.emit();
return;
}
};

let soroban_version = match toml
.get("dependencies")
.and_then(|d| d.get("soroban-sdk").and_then(|i| i.get("version")))
{
Some(version) => version.to_string(),
None => {
cx.sess()
.struct_warn("Soroban dependency not found in Cargo.toml")
.emit();
return;
}
};

let req = match Version::parse(&latest_version.replace('\"', "")) {
Ok(version) => version,
Err(e) => {
cx.sess()
.struct_warn(format!("Error parsing latest Soroban version: {}", e))
.emit();
return;
}
};

let soroban_version = match VersionReq::parse(&soroban_version.replace('\"', "")) {
Ok(version) => version,
Err(e) => {
cx.sess()
.struct_warn(format!("Error parsing project's Soroban version: {}", e))
.emit();
return;
}
};

if !soroban_version.matches(&req) {
Detector::SorobanVersion.span_lint_and_help(
cx,
CHECK_SOROBAN_VERSION,
rustc_span::DUMMY_SP,
&format!(r#"The latest Soroban version is {latest_version}, and your version is "{soroban_version}""#),
);
}
}
}

fn get_version() -> Result<String, String> {
let response = ureq::get("https://crates.io/api/v1/crates/soroban-sdk")
.set("User-Agent", "Scout/1.0")
.call();

match response {
Ok(resp) => {
let json: Result<serde_json::Value, Error> = resp.into_json();
match json {
Ok(json) => json
.get("crate")
.and_then(|c| c.get("max_stable_version"))
.map(|v| v.to_string())
.ok_or_else(|| "Failed to parse Soroban version from response".to_string()),
Err(_) => Err("Failed to parse response from crates.io".to_string()),
}
}
Err(_) => Err("Failed to get Soroban version from crates.io".to_string()),
}
}
4 changes: 3 additions & 1 deletion scout-audit-internal/src/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum Detector {
InsufficientlyRandomValues,
OverflowCheck,
SetContractStorage,
SorobanVersion,
UnprotectedUpdateCurrentContractWasm,
UnsafeExpect,
UnsafeUnwrap,
Expand All @@ -49,6 +50,7 @@ impl Detector {
Detector::InsufficientlyRandomValues => INSUFFICIENTLY_RANDOM_VALUES_LINT_MESSAGE,
Detector::OverflowCheck => OVERFLOW_CHECK_LINT_MESSAGE,
Detector::SetContractStorage => SET_CONTRACT_STORAGE_LINT_MESSAGE,
Detector::SorobanVersion => SOROBAN_VERSION_LINT_MESSAGE,
Detector::UnprotectedUpdateCurrentContractWasm => {
UNPROTECTED_UPDATE_CURRENT_CONTRACT_LINT_MESSAGE
}
Expand Down Expand Up @@ -83,7 +85,7 @@ fn print_scout_output(lint: Lint, span: Span) {
.map(|s| s.trim().to_string())
.collect();

let no_span_detectors = ["OVERFLOW_CHECK"];
let no_span_detectors = ["OVERFLOW_CHECK", "CHECK_SOROBAN_VERSION"];

if no_span_detectors.contains(&lint.name.to_owned().as_str()) {
let span = json!({
Expand Down
1 change: 1 addition & 0 deletions scout-audit-internal/src/detector/lint_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub const INSUFFICIENTLY_RANDOM_VALUES_LINT_MESSAGE: &str =
"Use env.prng() to generate random numbers, and remember that all random numbers are under the control of validators";
pub const OVERFLOW_CHECK_LINT_MESSAGE: &str = "Use `overflow-checks = true` in Cargo.toml profile";
pub const SET_CONTRACT_STORAGE_LINT_MESSAGE:&str = "Abitrary users should not have control over keys because it implies writing any value of left mapping, lazy variable, or the main struct of the contract located in position 0 of the storage";
pub const SOROBAN_VERSION_LINT_MESSAGE: &str = "Use the latest version of Soroban";
pub const UNPROTECTED_UPDATE_CURRENT_CONTRACT_LINT_MESSAGE: &str =
"This update_current_contract_wasm is called without access control";
pub const UNSAFE_EXPECT_LINT_MESSAGE: &str = "Unsafe usage of `expect`";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "soroban-version-remediated-1"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = { version = "=20.0.3" }

[dev_dependencies]
soroban-sdk = { version = "=20.0.3", features = ["testutils"] }

[features]
testutils = ["soroban-sdk/testutils"]

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true

[profile.release-with-logs]
inherits = "release"
debug-assertions = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_std]

use soroban_sdk::{contract, contractimpl};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
// Empty contract - vulnerability is in the Cargo.toml
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "soroban-version-vulnerable-1"
version = "0.1.0"
edition = "2021"


[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = { version = "=20.0.0" }

[dev_dependencies]
soroban-sdk = { version = "=20.0.0", features = ["testutils"] }

[features]
testutils = ["soroban-sdk/testutils"]

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true

[profile.release-with-logs]
inherits = "release"
debug-assertions = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_std]

use soroban_sdk::{contract, contractimpl};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
// Empty contract - vulnerability is in the Cargo.toml
}

0 comments on commit 3feb33b

Please sign in to comment.