Skip to content

Commit 019c3c6

Browse files
authored
Lazy values not set (#265)
* The imports of messages from scout-audit-internal were removed. Now, the detector information is exposed in the library by exporting the 'lint_info' function defined by the macros of 'scout-audit-dylint-linting'. * removed cli app (now on https://github.com/CoinFabrik/scout-audit) added ci scripts * remove branch from ci * first version of lazy-values-not-set detector * Documentation added, code improved to allow the use of attributes to change lint level * fix * Folder structure fixed * run cargo clippy * remove readme.md * fix on another test case for ci pass * changed detector number * fix
1 parent 3fad7c6 commit 019c3c6

File tree

13 files changed

+770
-7
lines changed

13 files changed

+770
-7
lines changed

.github/workflows/test-detectors.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ jobs:
119119
with:
120120
path: ~/.cargo
121121
key: ${{ runner.os }}-tests-${{ hashFiles('**/Cargo.lock') }}.
122-
# This is broken until ink! solves stdsimd problem.
123-
# - name: Run unit and integration tests
124-
# run: python scripts/run-tests.py --detector=${{ matrix.detector }}
122+
# This is broken until ink! solves stdsimd problem.
123+
# - name: Run unit and integration tests
124+
# run: python scripts/run-tests.py --detector=${{ matrix.detector }}
125125

126126
comment-on-pr:
127127
name: Comment on PR

detectors/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ dylint_testing = "3.0.0"
77
if_chain = "1.0.2"
88

99
scout-audit-clippy-utils = { version = "=0.2.3" }
10-
11-
itertools = { version = "0.12" }
10+
scout-audit-internal = { version = "=0.2.3", features = ["detector", "lint_helper"] }
1211
dylint_linting = { package = "scout-audit-dylint-linting", version = "3.0.1" }
12+
itertools = { version = "0.12" }
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "lazy-values-not-set"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
scout-audit-clippy-utils = { workspace = true }
11+
dylint_linting = { workspace = true }
12+
if_chain = { workspace = true }
13+
14+
15+
[dev-dependencies]
16+
dylint_testing = { workspace = true }
17+
18+
[package.metadata.rust-analyzer]
19+
rustc_private = true
+330
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
#![feature(rustc_private)]
2+
#![recursion_limit = "256"]
3+
4+
extern crate rustc_ast;
5+
extern crate rustc_hir;
6+
extern crate rustc_middle;
7+
extern crate rustc_span;
8+
9+
use std::collections::HashMap;
10+
11+
use rustc_hir::def_id::DefId;
12+
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
13+
use rustc_hir::{Body, FnDecl};
14+
use rustc_lint::{LateContext, LateLintPass};
15+
use rustc_middle::mir::traversal::preorder;
16+
use rustc_middle::mir::{Local, Operand, Rvalue, TerminatorKind};
17+
use rustc_middle::ty::TyKind;
18+
use rustc_span::def_id::LocalDefId;
19+
use rustc_span::Span;
20+
use scout_audit_clippy_utils::match_def_path;
21+
22+
const LINT_MESSAGE: &str = "Lazy value was gotten here but never set afterwards";
23+
24+
dylint_linting::impl_late_lint! {
25+
pub LAZY_VALUES_NOT_SET,
26+
Warn,
27+
LINT_MESSAGE,
28+
LazyValuesNotSet::default(),
29+
{
30+
name: "Lazy values get and not set",
31+
long_message: "When a get is performed, a copy of the value is received; if that copy is modified, the new value must be set afterwards.",
32+
severity: "Critical",
33+
help: "https://coinfabrik.github.io/scout/docs/vulnerabilities/lazy-values-not-set",
34+
vulnerability_class: "Best practices",
35+
}
36+
}
37+
38+
#[derive(Default)]
39+
pub struct LazyValuesNotSet {
40+
lazy_set_defid: Option<DefId>,
41+
lazy_get_defid: Option<DefId>,
42+
mapping_insert_defid: Option<DefId>,
43+
mapping_get_defid: Option<DefId>,
44+
}
45+
46+
struct FunFinderVisitor<'a, 'tcx: 'a> {
47+
cx: &'a LateContext<'tcx>,
48+
lazy_set_defid: Option<DefId>,
49+
lazy_get_defid: Option<DefId>,
50+
mapping_insert_defid: Option<DefId>,
51+
mapping_get_defid: Option<DefId>,
52+
}
53+
54+
impl<'a, 'tcx> Visitor<'tcx> for FunFinderVisitor<'a, 'tcx> {
55+
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
56+
if let rustc_hir::ExprKind::MethodCall(path, receiver, _, _) = expr.kind {
57+
if path.ident.to_string().contains("get")
58+
|| path.ident.to_string().contains("set")
59+
|| path.ident.to_string().contains("insert")
60+
{
61+
let defid = self.cx.typeck_results().type_dependent_def_id(expr.hir_id);
62+
63+
let receiver_type = self.cx.typeck_results().expr_ty(receiver);
64+
if let TyKind::Adt(def, _) = receiver_type.kind() {
65+
if match_def_path(self.cx, def.did(), &["ink_storage", "lazy", "Lazy"]) {
66+
if path.ident.to_string().contains("get") {
67+
self.lazy_get_defid = defid;
68+
} else {
69+
self.lazy_set_defid = defid;
70+
}
71+
} else if match_def_path(
72+
self.cx,
73+
def.did(),
74+
&["ink_storage", "lazy", "mapping", "Mapping"],
75+
) {
76+
if path.ident.to_string().contains("get") {
77+
self.mapping_get_defid = defid;
78+
} else {
79+
self.mapping_insert_defid = defid;
80+
}
81+
}
82+
}
83+
}
84+
}
85+
walk_expr(self, expr);
86+
}
87+
}
88+
89+
impl<'tcx> LateLintPass<'tcx> for LazyValuesNotSet {
90+
fn check_fn(
91+
&mut self,
92+
cx: &LateContext<'tcx>,
93+
_: FnKind<'tcx>,
94+
_: &'tcx FnDecl<'_>,
95+
body: &'tcx Body<'_>,
96+
_: Span,
97+
id: LocalDefId,
98+
) {
99+
//search for the defids of the different functions
100+
let mut visitor = FunFinderVisitor {
101+
cx,
102+
lazy_set_defid: None,
103+
lazy_get_defid: None,
104+
mapping_insert_defid: None,
105+
mapping_get_defid: None,
106+
};
107+
visitor.visit_expr(body.value);
108+
if visitor.lazy_set_defid.is_some() {
109+
self.lazy_set_defid = visitor.lazy_set_defid;
110+
}
111+
if visitor.lazy_get_defid.is_some() {
112+
self.lazy_get_defid = visitor.lazy_get_defid;
113+
}
114+
if visitor.mapping_insert_defid.is_some() {
115+
self.mapping_insert_defid = visitor.mapping_insert_defid;
116+
}
117+
if visitor.mapping_get_defid.is_some() {
118+
self.mapping_get_defid = visitor.mapping_get_defid;
119+
}
120+
121+
let (_, hm) = self.get_func_info(cx, id.to_def_id(), &[], &[], &mut vec![]);
122+
for val in hm.values() {
123+
scout_audit_clippy_utils::diagnostics::span_lint(
124+
cx,
125+
LAZY_VALUES_NOT_SET,
126+
*val,
127+
LINT_MESSAGE,
128+
);
129+
}
130+
}
131+
}
132+
133+
fn clean_local_upwards(local: Local, hm: &HashMap<Local, Vec<Local>>) -> Vec<Local> {
134+
let val = hm.get(&local);
135+
let mut ret_vec: Vec<Local> = vec![];
136+
if let Some(locals_vec) = val {
137+
ret_vec.extend(locals_vec);
138+
for local in locals_vec {
139+
ret_vec.extend(clean_local_upwards(*local, hm))
140+
}
141+
}
142+
ret_vec.dedup();
143+
ret_vec
144+
}
145+
146+
impl LazyValuesNotSet {
147+
fn get_func_info(
148+
&mut self,
149+
cx: &LateContext,
150+
func_defid: DefId,
151+
tainted_get_map: &[Local],
152+
tainted_get_lazy: &[Local],
153+
visited_funs: &mut Vec<DefId>,
154+
) -> (Vec<Local>, HashMap<Local, Span>) {
155+
if visited_funs.contains(&func_defid) {
156+
return (vec![], HashMap::new());
157+
}
158+
visited_funs.push(func_defid);
159+
let mir = cx.tcx.optimized_mir(func_defid);
160+
let mir_preorder = preorder(mir);
161+
let mut mapping_get_tainted_args: Vec<Local> = tainted_get_map.to_owned();
162+
let mut lazy_get_tainted_args: Vec<Local> = tainted_get_lazy.to_owned();
163+
let mut span_local: HashMap<Local, Span> = HashMap::new();
164+
let mut locals_dependencies: HashMap<Local, Vec<Local>> = HashMap::new();
165+
let mut locals_to_clean: Vec<Local> = vec![];
166+
for basicblock in mir_preorder {
167+
for stmt in basicblock.1.statements.iter().rev() {
168+
if let rustc_middle::mir::StatementKind::Assign(box_) = &stmt.kind {
169+
let locals = get_locals_in_rvalue(&box_.1);
170+
locals_dependencies.insert(box_.0.local, locals.clone());
171+
for local in locals {
172+
if mapping_get_tainted_args.contains(&local) {
173+
mapping_get_tainted_args.push(box_.0.local);
174+
}
175+
if lazy_get_tainted_args.contains(&local) {
176+
lazy_get_tainted_args.push(box_.0.local);
177+
}
178+
}
179+
}
180+
}
181+
if let Some(terminator) = &basicblock.1.terminator {
182+
if let TerminatorKind::Call {
183+
func,
184+
args,
185+
destination,
186+
fn_span,
187+
..
188+
} = &terminator.kind
189+
{
190+
match func {
191+
rustc_middle::mir::Operand::Copy(_)
192+
| rustc_middle::mir::Operand::Move(_) => {}
193+
rustc_middle::mir::Operand::Constant(b) => {
194+
if let TyKind::FnDef(defid, _args) = b.ty().kind() {
195+
//if the function is set or insert taint destinations local
196+
if self.lazy_get_defid.is_some_and(|did| did == *defid) {
197+
lazy_get_tainted_args.push(destination.local);
198+
span_local.insert(destination.local, *fn_span);
199+
} else if self.mapping_get_defid.is_some_and(|did| did == *defid) {
200+
mapping_get_tainted_args.push(destination.local);
201+
span_local.insert(destination.local, *fn_span);
202+
}
203+
//if the function is defined in the local crate call get_func_info recursively
204+
else if defid.is_local() {
205+
//translate from my locals to the locals into the call
206+
let mut mapping_args: Vec<Local> = vec![];
207+
let mut lazy_args: Vec<Local> = vec![];
208+
for arg in args.iter().enumerate() {
209+
match arg.1 {
210+
Operand::Copy(a) | Operand::Move(a) => {
211+
if mapping_get_tainted_args.contains(&a.local) {
212+
mapping_args.push(Local::from_usize(arg.0 + 1));
213+
}
214+
if lazy_get_tainted_args.contains(&a.local) {
215+
lazy_args.push(Local::from_usize(arg.0 + 1));
216+
}
217+
}
218+
Operand::Constant(_) => {}
219+
}
220+
}
221+
let cleaned_taints = self.get_func_info(
222+
cx,
223+
*defid,
224+
&mapping_args,
225+
&lazy_args,
226+
visited_funs,
227+
);
228+
//get the locals translated from call locals
229+
for local in cleaned_taints.0 {
230+
let op_arg = args.get(local.as_usize() - 1);
231+
if let Some(arg) = op_arg {
232+
match arg {
233+
Operand::Copy(a) | Operand::Move(a) => {
234+
//clean the taints
235+
mapping_get_tainted_args
236+
.retain(|i| a.local != *i);
237+
lazy_get_tainted_args.retain(|i| a.local != *i);
238+
//push locals to be cleaned before
239+
locals_to_clean.push(a.local)
240+
}
241+
Operand::Constant(_) => {}
242+
}
243+
}
244+
}
245+
}
246+
//if is an insert call clean the taints upwards
247+
else if self.lazy_set_defid.is_some_and(|did| did == *defid) {
248+
for arg in args {
249+
match arg {
250+
Operand::Copy(a) | Operand::Move(a) => {
251+
locals_to_clean.push(a.local);
252+
}
253+
Operand::Constant(_) => {}
254+
}
255+
}
256+
} else if self.mapping_insert_defid.is_some_and(|did| did == *defid)
257+
{
258+
for arg in args {
259+
match arg {
260+
Operand::Copy(a) | Operand::Move(a) => {
261+
locals_to_clean.push(a.local);
262+
}
263+
Operand::Constant(_) => {}
264+
}
265+
}
266+
} else {
267+
let mut args_locals = vec![];
268+
for arg in args {
269+
match arg {
270+
Operand::Copy(a) | Operand::Move(a) => {
271+
args_locals.push(a.local);
272+
}
273+
Operand::Constant(_) => {}
274+
}
275+
}
276+
locals_dependencies.insert(destination.local, args_locals);
277+
}
278+
}
279+
}
280+
}
281+
}
282+
}
283+
}
284+
for local in locals_to_clean.clone() {
285+
locals_to_clean.extend(clean_local_upwards(local, &locals_dependencies));
286+
}
287+
locals_to_clean.dedup();
288+
for local in &locals_to_clean {
289+
span_local.remove(local);
290+
}
291+
(
292+
locals_to_clean
293+
.clone()
294+
.into_iter()
295+
.filter(|l| l.as_usize() <= mir.arg_count)
296+
.collect::<Vec<Local>>(),
297+
span_local,
298+
)
299+
}
300+
}
301+
302+
fn get_locals_in_rvalue(rvalue: &Rvalue) -> Vec<Local> {
303+
fn op_local(op: &Operand) -> Vec<Local> {
304+
match op {
305+
rustc_middle::mir::Operand::Copy(p) | rustc_middle::mir::Operand::Move(p) => {
306+
vec![p.local]
307+
}
308+
rustc_middle::mir::Operand::Constant(_) => vec![],
309+
}
310+
}
311+
match rvalue {
312+
rustc_middle::mir::Rvalue::Use(op)
313+
| rustc_middle::mir::Rvalue::Repeat(op, _)
314+
| rustc_middle::mir::Rvalue::Cast(_, op, _)
315+
| rustc_middle::mir::Rvalue::UnaryOp(_, op) => op_local(op),
316+
rustc_middle::mir::Rvalue::Ref(_, _, p)
317+
| rustc_middle::mir::Rvalue::AddressOf(_, p)
318+
| rustc_middle::mir::Rvalue::Len(p)
319+
| rustc_middle::mir::Rvalue::CopyForDeref(p) => {
320+
vec![p.local]
321+
}
322+
rustc_middle::mir::Rvalue::BinaryOp(_, ops)
323+
| rustc_middle::mir::Rvalue::CheckedBinaryOp(_, ops) => {
324+
let mut v = op_local(&ops.0);
325+
v.extend(op_local(&ops.1));
326+
v
327+
}
328+
_ => vec![],
329+
}
330+
}

0 commit comments

Comments
 (0)