flowistry_ifc/
analysis.rs

1#![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}