Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

57 pragma declarations and version control #64

Merged
merged 7 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}