flowistry_ifc/
analysis.rs1#![allow(dead_code)]
2
3use std::io::Write;
4
5use anyhow::Result;
6use flowistry::{infoflow::FlowResults, mir::utils::PlaceSet};
7use rustc_data_structures::fx::FxHashMap as HashMap;
8use rustc_hir::{BodyId, def::Res, def_id::DefId};
9use rustc_infer::traits::EvaluationResult;
10use rustc_middle::{
11 mir::*,
12 ty::{ParamEnv, Ty, TyCtxt, TypingMode},
13};
14use rustc_mir_dataflow::JoinSemiLattice;
15use rustc_span::FileName;
16use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};
17use rustc_utils::{BodyExt, PlaceExt, SpanExt};
18use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
19
20fn implements_trait<'tcx>(
21 tcx: TyCtxt<'tcx>,
22 param_env: ParamEnv<'tcx>,
23 ty: Ty<'tcx>,
24 trait_def_id: DefId,
25) -> bool {
26 let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
27 let ty = tcx.erase_regions(ty);
28 let result = infcx.type_implements_trait(trait_def_id, [ty], param_env);
29 matches!(
30 result,
31 EvaluationResult::EvaluatedToOk | EvaluationResult::EvaluatedToOkModuloRegions
32 )
33}
34
35pub enum IssueFound {
36 Yes,
37 No,
38}
39
40pub fn analyze(body_id: &BodyId, results: &FlowResults) -> Result<IssueFound> {
41 let tcx = results.analysis.tcx;
42 let body = results.analysis.body;
43 let def_id = tcx.hir_body_owner_def_id(*body_id).to_def_id();
44
45 log::debug!(
46 "Crates: {:?}",
47 tcx
48 .crates(())
49 .iter()
50 .map(|krate| tcx.crate_name(*krate))
51 .collect::<Vec<_>>()
52 );
53 let ifc_crate = match tcx
54 .crates(())
55 .iter()
56 .find(|krate| tcx.crate_name(**krate).as_str() == "flowistry_ifc_traits")
57 {
58 Some(c) => *c,
59 None => {
60 return Ok(IssueFound::No);
61 }
62 };
63
64 let ifc_mod = DefId {
65 krate: ifc_crate,
66 index: rustc_hir::def_id::CRATE_DEF_INDEX,
67 };
68 let ifc_items = tcx
69 .module_children(ifc_mod)
70 .iter()
71 .filter_map(|export| match export.res {
72 Res::Def(_, id) => Some((export.ident.to_string(), id)),
73 _ => None,
74 })
75 .collect::<HashMap<_, _>>();
76
77 let all_places = body
78 .local_decls()
79 .indices()
80 .flat_map(|local| {
81 let place = Place::from_local(local, tcx);
82 place.interior_places(tcx, body, def_id)
83 })
84 .collect::<PlaceSet>();
85
86 let find_implements = |trait_def_id| -> PlaceSet {
87 all_places
88 .iter()
89 .copied()
90 .filter(|place| {
91 let ty = place.ty(body.local_decls(), tcx).ty;
92 implements_trait(tcx, tcx.param_env(def_id), ty, trait_def_id)
93 })
94 .collect()
95 };
96 let secure_places = find_implements(ifc_items["Secure"]);
97 let insecure_places = find_implements(ifc_items["Insecure"]);
98
99 let final_state = body
100 .all_returns()
101 .map(|location| results.state_at(location).clone())
102 .reduce(|mut a, b| {
103 a.join(&b);
104 a
105 })
106 .unwrap();
107
108 let mut errors = Vec::new();
109 for secure in secure_places.iter() {
110 let secure_deps = results.analysis.deps_for(&final_state, *secure);
111 for insecure in insecure_places.iter() {
112 let insecure_deps = results.analysis.deps_for(&final_state, *insecure);
113 if insecure_deps.is_superset(&secure_deps) {
114 errors.push((secure, insecure));
115 }
116 }
117 }
118
119 let mut stdout = StandardStream::stderr(ColorChoice::Auto);
120 let mut black_spec = ColorSpec::new();
121 black_spec.set_fg(Some(Color::Yellow));
122 let mut red_spec = ColorSpec::new();
123 red_spec.set_fg(Some(Color::Red));
124
125 let decls = body.local_decls();
126 let source_map = tcx.sess.source_map();
127 let filename = match source_map.span_to_filename(body.span) {
128 FileName::Real(f) => f,
129 _ => unimplemented!(),
130 };
131 let has_errors = !errors.is_empty();
132 for (src, dst) in errors {
133 let body_span = tcx.hir_span_with_body(body_id.hir_id);
134 let src_span = decls[src.local].source_info.span.as_local(body_span);
135 let dst_span = decls[dst.local].source_info.span.as_local(body_span);
136
137 let span_range = |span| match span {
138 Some(span) => {
139 let lines = source_map.span_to_lines(span).unwrap();
140 let first = lines.lines.first().unwrap();
141 let last = lines.lines.last().unwrap();
142 format!(
143 "{}:{}-{}:{}",
144 first.line_index + 1,
145 first.start_col.0,
146 last.line_index + 1,
147 last.end_col.0
148 )
149 }
150 None => "<in macro expansion>".to_owned(),
151 };
152
153 let span_contents = |span| match span {
154 Some(span) => source_map.span_to_snippet(span).unwrap(),
155 None => "<in macro expansion>".to_owned(),
156 };
157
158 stdout.set_color(&red_spec)?;
159 writeln!(
160 stdout,
161 "ERROR: insecure flow in {filename} from data at {src_span}:",
162 filename = filename
163 .local_path_if_available()
164 .file_name()
165 .unwrap()
166 .to_string_lossy(),
167 src_span = span_range(src_span)
168 )?;
169
170 stdout.set_color(&black_spec)?;
171 writeln!(
172 stdout,
173 " {src_snippet}",
174 src_snippet = span_contents(src_span)
175 )?;
176
177 stdout.set_color(&red_spec)?;
178 writeln!(
179 stdout,
180 "to data at {dst_span}:",
181 dst_span = span_range(dst_span)
182 )?;
183
184 stdout.set_color(&black_spec)?;
185 writeln!(
186 stdout,
187 " {dst_snippet}\n",
188 dst_snippet = span_contents(dst_span)
189 )?;
190 }
191
192 Ok(if has_errors {
193 IssueFound::Yes
194 } else {
195 IssueFound::No
196 })
197}