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

unsafe-block test case and detector #38

Merged
merged 31 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
20fda2f
Added unsafe-block test case
faculerena Nov 30, 2023
80c2fb7
Merge branch 'main' into unsafe-block
faculerena Dec 1, 2023
6317856
first iteration of unsafe-block detector
faculerena Dec 1, 2023
fff536b
change name on impl
faculerena Dec 1, 2023
faccbfd
cargo fmt
faculerena Dec 4, 2023
64c77b5
changed core-mem-forget to avoid-..
faculerena Dec 6, 2023
1a993ac
changed unsafe-block to avoid-*
faculerena Dec 6, 2023
ddf57e3
Update test-detectors.yml
faculerena Dec 6, 2023
ec9dab0
merge main
faculerena Dec 6, 2023
f3d866b
Merge branch 'core-mem-forget' of github.com:CoinFabrik/scout-soroban…
faculerena Dec 6, 2023
42981d8
Merge branch 'main' into core-mem-forget
faculerena Dec 6, 2023
d92a8f8
Merge branch 'main' into unsafe-block
faculerena Dec 6, 2023
4b10b56
forgot one name
faculerena Dec 6, 2023
c413c80
Merge branch 'core-mem-forget' of github.com:CoinFabrik/scout-soroban…
faculerena Dec 6, 2023
b570602
removed old name
faculerena Dec 6, 2023
a0baa26
package names
faculerena Dec 6, 2023
b667b5c
dup
faculerena Dec 6, 2023
c44d96e
dup
faculerena Dec 6, 2023
6aeec54
dup again
faculerena Dec 6, 2023
3035dfa
merge next branch
faculerena Dec 6, 2023
7a8c93c
changed to avoid-
faculerena Dec 6, 2023
27e64ed
detector fix
faculerena Dec 7, 2023
d3f4992
removed file
faculerena Dec 7, 2023
8a8b180
wrong folder name
faculerena Dec 7, 2023
104917e
detectors should be in alphabetical order or CI fails
faculerena Dec 7, 2023
6e0ab3a
Merge branch 'main' into unsafe-block
arlosiggio Dec 12, 2023
81be4eb
remove redefine in lint_message.rs
arlosiggio Dec 12, 2023
7a4eb6c
Merge branch 'main' into unsafe-block
faculerena Dec 15, 2023
e7c9f71
review changes
faculerena Dec 20, 2023
70bc352
Merge branch 'main' into unsafe-block
faculerena Dec 20, 2023
b6018a6
Update lint_message.rs
faculerena Dec 20, 2023
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 @@ -98,6 +98,7 @@ jobs:
test:
[
"avoid-core-mem-forget",
"avoid-unsafe-block",
"divide-before-multiply",
"insufficiently-random-values",
"overflow-check",
Expand Down
20 changes: 20 additions & 0 deletions detectors/avoid-unsafe-block/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "avoid-unsafe-block"
version = "0.1.0"
edition = "2021"

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

[dependencies]
clippy_utils = { workspace = true }
faculerena marked this conversation as resolved.
Show resolved Hide resolved
dylint_linting = { workspace = true }
if_chain = { workspace = true }

scout-audit-internal = { workspace = true }

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

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

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

use rustc_hir::{
intravisit::{walk_expr, Visitor},
Expr, ExprKind,
};
use rustc_lint::LateLintPass;
use rustc_span::Span;
use scout_audit_internal::Detector;

dylint_linting::declare_late_lint! {
/// ### What it does
/// Checks for usage of `unsafe` blocks.
///
/// ### Why is this bad?
/// `unsafe` blocks should not be used unless absolutely necessary.
///
/// ### Example
/// ```rust
///pub fn unsafe_function(n: u64) -> u64 {
/// unsafe {
/// let mut i = n as f64;
/// let mut y = i.to_bits();
/// y = 0x5fe6ec85e7de30da - (y >> 1);
/// i = f64::from_bits(y);
/// i *= 1.5 - 0.5 * n as f64 * i * i;
/// i *= 1.5 - 0.5 * n as f64 * i * i;
///
/// let result_ptr: *mut f64 = &mut i;
/// let result = *result_ptr;
///
/// result.to_bits()
/// }
///}
/// Use instead:
/// ```rust
///pub fn unsafe_function(n: u64) -> u64 {
/// let mut i = n as f64;
/// let mut y = i.to_bits();
/// y = 0x5fe6ec85e7de30da - (y >> 1);
/// i = f64::from_bits(y);
/// i *= 1.5 - 0.5 * n as f64 * i * i;
/// i *= 1.5 - 0.5 * n as f64 * i * i;
/// result.to_bits()
///}
/// ```
pub AVOID_UNSAFE_BLOCK,
Warn,
Detector::AvoidUnsafeBlock.get_lint_message()
}

impl<'tcx> LateLintPass<'tcx> for AvoidUnsafeBlock {
fn check_fn(
&mut self,
cx: &rustc_lint::LateContext<'tcx>,
_: rustc_hir::intravisit::FnKind<'tcx>,
_: &'tcx rustc_hir::FnDecl<'tcx>,
body: &'tcx rustc_hir::Body<'tcx>,
_: rustc_span::Span,
_: rustc_hir::def_id::LocalDefId,
) {
struct UnsafeBlockVisitor {
unsafe_blocks: Vec<Option<Span>>,
}

impl<'tcx> Visitor<'tcx> for UnsafeBlockVisitor {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let ExprKind::Block(block, _) = expr.kind {
if block.rules
== rustc_hir::BlockCheckMode::UnsafeBlock(
rustc_hir::UnsafeSource::UserProvided,
)
{
self.unsafe_blocks.push(Some(expr.span));
} else {
self.unsafe_blocks.push(None);
}
faculerena marked this conversation as resolved.
Show resolved Hide resolved
}

walk_expr(self, expr);
}
}

let mut visitor = UnsafeBlockVisitor {
unsafe_blocks: Vec::new(),
};

walk_expr(&mut visitor, body.value);

visitor.unsafe_blocks.iter().for_each(|span| {
if let Some(span) = span {
Detector::AvoidUnsafeBlock.span_lint(cx, AVOID_UNSAFE_BLOCK, *span);
}
});
}
}
2 changes: 2 additions & 0 deletions scout-audit-internal/src/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use strum::{Display, EnumIter};
#[strum(serialize_all = "kebab-case")]
pub enum Detector {
AvoidCoreMemForget,
AvoidUnsafeBlock,
DivideBeforeMultiply,
InsufficientlyRandomValues,
OverflowCheck,
Expand All @@ -49,6 +50,7 @@ impl Detector {
Detector::UnprotectedUpdateCurrentContractWasm => {
UNPROTECTED_UPDATE_CURRENT_CONTRACT_LINT_MESSAGE
}
Detector::AvoidUnsafeBlock => AVOID_UNSAFE_BLOCK_LINT_MESSAGE,
Detector::UnsafeExpect => UNSAFE_EXPECT_LINT_MESSAGE,
Detector::UnsafeUnwrap => UNSAFE_UNWRAP_LINT_MESSAGE,
}
Expand Down
2 changes: 2 additions & 0 deletions scout-audit-internal/src/detector/lint_message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub const AVOID_CORE_MEM_FORGET_LINT_MESSAGE: &str =
"Use the `let _ = ...` pattern or `.drop()` method to forget the value";
pub const AVOID_UNSAFE_BLOCK_LINT_MESSAGE: &str =
"Avoid using unsafe blocks as it may lead to undefined behavior";
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";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "avoid-unsafe-block-1-remediated"
version = "0.1.0"
edition = "2021"

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

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

[dev_dependencies]
soroban-sdk = { version = "20.0.0-rc2", features = ["testutils"] }
faculerena marked this conversation as resolved.
Show resolved Hide resolved

[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,33 @@
#![no_std]
use soroban_sdk::{contract, contractimpl};
#[contract]
pub struct AvoidUnsafeBlock;
#[contractimpl]
impl AvoidUnsafeBlock {
pub fn unsafe_function(n: u64) -> u64 {
let mut i = n as f64;
let mut y = i.to_bits();
y = 0x5fe6ec85e7de30da - (y >> 1);
i = f64::from_bits(y);
i *= 1.5 - 0.5 * n as f64 * i * i;
i *= 1.5 - 0.5 * n as f64 * i * i;
i.to_bits()
}
}
#[cfg(test)]
mod tests {
use crate::AvoidUnsafeBlock;
#[test]
fn test_unsafe_block() {
let test_value = 8;
let result = AvoidUnsafeBlock::unsafe_function(test_value);
let inverse = inverse_square_root_without_unsafe(test_value);

assert_eq!((inverse - result) / inverse, 0);
assert_eq!((inverse - result) / result, 0);
}

fn inverse_square_root_without_unsafe(n: u64) -> u64 {
(1.0 / (n as f64).sqrt()).to_bits()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "avoid-unsafe-block-1-vulnerable"
version = "0.1.0"
edition = "2021"

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

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

[dev_dependencies]
soroban-sdk = { version = "20.0.0-rc2", features = ["testutils"] }
faculerena marked this conversation as resolved.
Show resolved Hide resolved

[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

[workspace.metadata.dylint]
libraries = [
{ path = "/home/flerena/coinfabrik/soroban/scout-soroban/detectors/unsafe-block"},
]
faculerena marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#![no_std]
use soroban_sdk::{contract, contractimpl};
#[contract]
pub struct AvoidUnsafeBlock;
#[contractimpl]
impl AvoidUnsafeBlock {
pub fn unsafe_function(n: u64) -> u64 {
unsafe {
let mut i = n as f64;
let mut y = i.to_bits();
y = 0x5fe6ec85e7de30da - (y >> 1);
i = f64::from_bits(y);
i *= 1.5 - 0.5 * n as f64 * i * i;
i *= 1.5 - 0.5 * n as f64 * i * i;

let result_ptr: *mut f64 = &mut i;

(*result_ptr).to_bits()
}
}
}
#[cfg(test)]
mod tests {
use crate::AvoidUnsafeBlock;
#[test]
fn test_unsafe_block() {
let test_value = 8;
let result = AvoidUnsafeBlock::unsafe_function(test_value);
let inverse = inverse_square_root_without_unsafe(test_value);

assert_eq!((inverse - result) / inverse, 0);
assert_eq!((inverse - result) / result, 0);
}

fn inverse_square_root_without_unsafe(n: u64) -> u64 {
(1.0 / (n as f64).sqrt()).to_bits()
}
}