diff --git a/.gitignore b/.gitignore index ece3a53..37277a3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,7 @@ target/ # Idea .idea/ +# VScode +.vscode/ + Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index d82408a..1916e5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,9 @@ members = [ "untrusted_value", "untrusted_value_derive", - "untrusted_value_derive_internals" -] + "untrusted_value_derive_internals", + "untrusted_value_taint_checker", + "untrusted_value_taint_checker/sample"] resolver = "2" + +exclude = ["rust"] \ No newline at end of file diff --git a/release-plz.toml b/release-plz.toml new file mode 100644 index 0000000..ecb6310 --- /dev/null +++ b/release-plz.toml @@ -0,0 +1,9 @@ +[workspace] + +[[package]] +name = "sample" +publish = false + +[[package]] +name = "untrusted_value_taint_checker" +publish = false diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..21ea6b5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,9 @@ +[toolchain] +channel = "nightly-2024-06-11" +components = [ + "rust-src", + "rustc-dev", + "llvm-tools-preview", + "rustfmt", + "rust-analyzer-preview" +] \ No newline at end of file diff --git a/untrusted_value_taint_checker/Cargo.toml b/untrusted_value_taint_checker/Cargo.toml new file mode 100644 index 0000000..405cb3a --- /dev/null +++ b/untrusted_value_taint_checker/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "untrusted_value_taint_checker" +version = "0.1.0" +edition = "2021" + +[dependencies] +untrusted_value = { version = "0.3.1", path = "../untrusted_value"} +anyhow = "1.0.86" +config = "0.14.0" +tracing-subscriber = { version = "0.3.18" , features = ["env-filter"]} +tracing = "0" +serde_json = "1.0.120" +tempfile = "3.10.1" +serde = { version = "1.0.204", features = ["derive"] } +#owo-colors = { version = "4", features = ["supports-colors"] } +#cargo_metadata = "0.18.1" +#bpaf = { version = "0.9.12", features = ["bpaf_derive", "autocomplete"] } + +[package.metadata.rust-analyzer] +rustc_private = true + +[build-dependencies] +serde = { version = "1.0.204", features = ["derive"] } +serde_json = "1.0.120" diff --git a/untrusted_value_taint_checker/README.md b/untrusted_value_taint_checker/README.md new file mode 100644 index 0000000..2ebe126 --- /dev/null +++ b/untrusted_value_taint_checker/README.md @@ -0,0 +1,3 @@ +TODO + +Some code parts inspired/copied from https://github.com/LiHRaM/taint/tree/master licensed unter MIT (licese notice can be found in opensource/LICENSE-LiHRam-taint) \ No newline at end of file diff --git a/untrusted_value_taint_checker/build.rs b/untrusted_value_taint_checker/build.rs new file mode 100644 index 0000000..06c694d --- /dev/null +++ b/untrusted_value_taint_checker/build.rs @@ -0,0 +1,114 @@ +use std::{fs, process::Command}; + +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub struct TaintModuleJson { + pub taint_module_name: String, + pub description: String, + pub content: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TaintModuleLibraryJson { + pub module_prefix: String, + pub taint_sources: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TaintSourceJson { + pub functions: Vec, + pub description: String, +} + +#[allow(clippy::useless_format)] +fn main() { + println!("cargo::rerun-if-changed=src/analysis/taint_source/"); + + // list files in taint_source directory + let taint_source_dir = std::path::Path::new("src/analysis/taint_source/"); + let taint_source_files = + std::fs::read_dir(taint_source_dir).expect("Cannot read taint_source directory"); + + let mut taint_modules = Vec::new(); + + for file in taint_source_files { + let file = file.expect("Cannot read directory entry"); + let file = file.path(); + if file.extension() != Some("json".as_ref()) { + continue; + } + let file = fs::File::open(file).expect("Cannot open file"); + + let taint_module: TaintModuleJson = + serde_json::from_reader(file).expect("Cannot parse JSON"); + taint_modules.push(taint_module); + } + + let mut generated_code = String::new(); + + generated_code.push_str(&format!("vec![\n")); + let mut number = 0; + for module in taint_modules { + generated_code.push_str(&format!(" TaintSourceDefinition {{\n")); + generated_code.push_str(&format!( + " taint_module_name: \"{}\",\n", + module.taint_module_name + )); + generated_code.push_str(&format!( + " taint_module_description: \"{}\",\n", + module.description + )); + generated_code.push_str(&format!(" sources: vec![\n")); + for library in module.content { + let prefix = if library.module_prefix.is_empty() { + "".to_owned() + } else { + format!("{}::", library.module_prefix) + }; + + for source in library.taint_sources { + generated_code.push_str(&format!(" TaintSource {{\n")); + generated_code.push_str(&format!( + " description: \"{}\",\n", + source.description + )); + generated_code.push_str(&format!(" functions: vec![\n")); + + for function in source.functions { + generated_code.push_str(&format!( + " \"{}{}\",\n", + prefix, function + )); + } + + generated_code.push_str(&format!(" ],}},\n")); + } + } + generated_code.push_str(&format!(" ],}},\n")); + number += 1; + } + generated_code.push_str(&format!(" ]")); + + let template = std::fs::read_to_string(std::path::Path::new( + "src/analysis/taint_source/generated.tpl", + )) + .expect("Cannot read template"); + let out_text = template + .replace("", &generated_code) + .replace("", format!("{number}").as_str()); + + let out_file = std::path::Path::new("src/analysis/taint_source/generated.rs"); + fs::write(out_file, out_text).expect("Cannot write generated code"); + + // run cargo fmt + let cmd = Command::new("rustfmt").arg(out_file).spawn(); + + let _ = cmd.map(|mut cmd| { + cmd.wait().map(|exit| { + if !exit.success() { + panic!("Rustfmt returned non-zero exit code.") + } + }) + }); +} diff --git a/untrusted_value_taint_checker/opensource/LICENSE-LiHRam-taint b/untrusted_value_taint_checker/opensource/LICENSE-LiHRam-taint new file mode 100644 index 0000000..9cf1062 --- /dev/null +++ b/untrusted_value_taint_checker/opensource/LICENSE-LiHRam-taint @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/untrusted_value_taint_checker/sample/Cargo.toml b/untrusted_value_taint_checker/sample/Cargo.toml new file mode 100644 index 0000000..01361a6 --- /dev/null +++ b/untrusted_value_taint_checker/sample/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sample" +version = "0.1.0" +edition = "2021" + +[dependencies] +untrusted_value = { version = "0", path = "../../untrusted_value" } \ No newline at end of file diff --git a/untrusted_value_taint_checker/sample/src/main.rs b/untrusted_value_taint_checker/sample/src/main.rs new file mode 100644 index 0000000..7f0af30 --- /dev/null +++ b/untrusted_value_taint_checker/sample/src/main.rs @@ -0,0 +1,26 @@ +use untrusted_value::UntrustedValue; + +#[no_mangle] +fn works() { + let insecure_env = std::env::var("TEST"); + + // do some stuff in between + println!("waiting..."); + std::thread::sleep(std::time::Duration::from_secs(10)); + + let secure_env = UntrustedValue::from(insecure_env); + + println!("{:?}", secure_env.use_untrusted_value()) +} + +#[no_mangle] +fn fails() { + let insecure_env = std::env::var("TEST"); + + println!("{:?}", insecure_env) +} + +fn main() { + works(); + fails(); +} diff --git a/untrusted_value_taint_checker/src/analysis/build_plan.rs b/untrusted_value_taint_checker/src/analysis/build_plan.rs new file mode 100644 index 0000000..dbfdf88 --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/build_plan.rs @@ -0,0 +1,33 @@ +use std::path::PathBuf; + +use config::Map; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct BuildPlan { + pub invocations: Vec, + pub inputs: Vec, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub enum CompileMode { + #[serde(rename = "run-custom-build")] + RunCustomBuild, + #[serde(rename = "build")] + Build, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct BuildInvocation { + pub package_name: String, + pub package_version: String, + pub target_kind: Vec, + pub kind: Option, + pub compile_mode: CompileMode, + pub outputs: Vec, + pub links: Map, + pub program: PathBuf, + pub args: Vec, + pub env: Map, + pub cwd: PathBuf, +} diff --git a/untrusted_value_taint_checker/src/analysis/callbacks.rs b/untrusted_value_taint_checker/src/analysis/callbacks.rs new file mode 100644 index 0000000..f0f4bbb --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/callbacks.rs @@ -0,0 +1,113 @@ +extern crate rustc_ast; +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_interface; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_driver::Compilation; +use rustc_hir::intravisit::Visitor; +use rustc_middle::ty::TyCtxt; +use tracing::{event, span, Level}; + +pub struct TaintCompilerCallbacks { + pub package_name: String, + pub package_version: String, + pub internal_interface_functions: Vec, +} + +impl TaintCompilerCallbacks { + pub fn cast_to_dyn(&mut self) -> &mut (dyn rustc_driver::Callbacks + Send) { + self + } +} + +impl rustc_driver::Callbacks for TaintCompilerCallbacks { + /// All the work we do happens after analysis, so that we can make assumptions about the validity of the MIR. + fn after_analysis<'tcx>( + &mut self, + compiler: &rustc_interface::interface::Compiler, + queries: &'tcx rustc_interface::Queries<'tcx>, + ) -> Compilation { + compiler.sess.dcx().abort_if_errors(); + enter_with_fn(queries, self, mir_analysis); + compiler.sess.dcx().abort_if_errors(); + Compilation::Continue + } +} + +fn enter_with_fn<'tcx, TyCtxtFn>( + queries: &'tcx rustc_interface::Queries<'tcx>, + callback_data: &mut TaintCompilerCallbacks, + enter_fn: TyCtxtFn, +) where + TyCtxtFn: Fn(TyCtxt, &mut TaintCompilerCallbacks), +{ + queries + .global_ctxt() + .unwrap() + .enter(move |context| enter_fn(context, callback_data)); +} + +pub struct FunctionInfo { + pub function_name: String, + pub body_id: rustc_hir::BodyId, + pub local_def_id: rustc_hir::def_id::LocalDefId, + pub span: rustc_span::Span, +} + +struct CrateVisitor<'a, 'tcx> { + pub tcx: &'a TyCtxt<'tcx>, + internal_functions: Vec, +} + +impl<'a, 'tcx> CrateVisitor<'a, 'tcx> { + pub fn new(tcx: &'a TyCtxt<'tcx>) -> Self { + Self { + tcx, + internal_functions: Vec::default(), + } + } +} + +impl<'v, 'a, 'tcx> Visitor<'v> for CrateVisitor<'a, 'tcx> { + fn visit_fn( + &mut self, + _kind: rustc_hir::intravisit::FnKind<'v>, + _decl: &'v rustc_hir::FnDecl<'v>, + body_id: rustc_hir::BodyId, + span: rustc_span::Span, + local_def_id: rustc_hir::def_id::LocalDefId, + ) -> Self::Result { + let function_name = self.tcx.def_path_str(local_def_id); + self.internal_functions.push(FunctionInfo { + body_id, + span, + local_def_id, + function_name, + }); + } +} + +pub fn mir_analysis(tcx: TyCtxt, callback_data: &mut TaintCompilerCallbacks) { + // let mut finder = TaintAttributeFinder::new(tcx); + + let span = span!(Level::TRACE, "Public interface analysis"); + let _enter = span.enter(); + + let mut hir_analysis = CrateVisitor::new(&tcx); + tcx.hir().visit_all_item_likes_in_crate(&mut hir_analysis); + + for finfo in &hir_analysis.internal_functions { + event!( + Level::TRACE, + function_name = finfo.function_name, + source_code = format!("{:?}", finfo.span) + ); + } + + callback_data.internal_interface_functions = + std::mem::take(&mut hir_analysis.internal_functions); +} diff --git a/untrusted_value_taint_checker/src/analysis/invocation_environment.rs b/untrusted_value_taint_checker/src/analysis/invocation_environment.rs new file mode 100644 index 0000000..4e84d72 --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/invocation_environment.rs @@ -0,0 +1,47 @@ +use std::env; +use std::path::PathBuf; + +use super::build_plan::BuildInvocation; + +pub struct InvocationEnvironment { + vars: Vec<(String, String)>, + override_vars: Vec<(String, String)>, + cwd: PathBuf, +} + +impl InvocationEnvironment { + pub fn enter(environment: &BuildInvocation) -> InvocationEnvironment { + let mut result = InvocationEnvironment { + vars: Vec::with_capacity(environment.env.len()), + cwd: env::current_dir().unwrap(), + override_vars: Vec::with_capacity(environment.env.len()), + }; + + for (key, value) in environment.env.iter() { + result.vars.push((key.to_owned(), value.to_owned())); + } + + env::set_current_dir(&environment.cwd).unwrap(); + + for (key, value) in environment.env.iter() { + env::set_var(key, value); + result.override_vars.push((key.clone(), value.clone())); + } + + result + } +} + +impl Drop for InvocationEnvironment { + fn drop(&mut self) { + for (key, _) in self.override_vars.iter() { + env::remove_var(key); + } + + for (key, value) in self.vars.iter() { + env::set_var(key, value); + } + + env::set_current_dir(&self.cwd).unwrap(); + } +} diff --git a/untrusted_value_taint_checker/src/analysis/taint_source.rs b/untrusted_value_taint_checker/src/analysis/taint_source.rs new file mode 100644 index 0000000..7f6f630 --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/taint_source.rs @@ -0,0 +1,19 @@ +#[derive(Debug, Clone)] + +pub struct TaintSourceDefinition<'a> { + pub taint_module_name: &'a str, + pub taint_module_description: &'a str, + pub sources: Vec>, +} + +#[derive(Debug, Clone)] +pub struct TaintSource<'a> { + pub functions: Vec<&'a str>, + pub description: &'a str, +} + +mod generated; + +pub fn get_taint_sources_definitions() -> Vec> { + generated::get_taint_sources_definitions() +} diff --git a/untrusted_value_taint_checker/src/analysis/taint_source/.gitignore b/untrusted_value_taint_checker/src/analysis/taint_source/.gitignore new file mode 100644 index 0000000..c3cfc5d --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/taint_source/.gitignore @@ -0,0 +1 @@ +generated.rs diff --git a/untrusted_value_taint_checker/src/analysis/taint_source/generated.tpl b/untrusted_value_taint_checker/src/analysis/taint_source/generated.tpl new file mode 100644 index 0000000..081197f --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/taint_source/generated.tpl @@ -0,0 +1,6 @@ +use super::{TaintSourceDefinition, TaintSource}; + +#[rustfmt::skip] +pub fn get_taint_sources_definitions() -> Vec> { + +} diff --git a/untrusted_value_taint_checker/src/analysis/taint_source/std.json b/untrusted_value_taint_checker/src/analysis/taint_source/std.json new file mode 100644 index 0000000..bcdc3a0 --- /dev/null +++ b/untrusted_value_taint_checker/src/analysis/taint_source/std.json @@ -0,0 +1,28 @@ +{ + "taint_module_name": "std", + "description": "Contains taint sources definitions for the standard library", + "content": [ + { + "module_prefix": "env", + "taint_sources": [ + { + "functions": ["args", "os_args", "vars", "vars_os", "var", "var_os"], + "description": "Environment arguments might be attacker controlled." + }, + { + "functions": ["current_exe"], + "description": "The current executable name is passed as argument thoughs migt be attacker controlled." + } + ] + }, + { + "module_prefix": "fs", + "taint_sources": [ + { + "functions": ["File::open"], + "description": "Files shoud be concidered potentially malicious." + } + ] + } + ] +} \ No newline at end of file diff --git a/untrusted_value_taint_checker/src/cargo.rs b/untrusted_value_taint_checker/src/cargo.rs new file mode 100644 index 0000000..2a870ff --- /dev/null +++ b/untrusted_value_taint_checker/src/cargo.rs @@ -0,0 +1,123 @@ +use std::{ + fs, + process::{Command, Stdio}, +}; + +use crate::{ + analysis::{ + build_plan::{BuildInvocation, BuildPlan, CompileMode}, + callbacks::TaintCompilerCallbacks, + invocation_environment::InvocationEnvironment, + }, + rustc, +}; +use anyhow::anyhow; + +pub fn execute_build_plan(build_plan: BuildPlan) -> anyhow::Result<()> { + let total_invocations = build_plan.invocations.len(); + + for (i, invocation) in build_plan.invocations.into_iter().enumerate() { + println!( + "Compiling {}/{}: {} v{}", + i + 1, + total_invocations, + invocation.package_name, + invocation.package_version + ); + + let links = invocation.links.clone(); + + match invocation.compile_mode { + CompileMode::Build => { + let results = execute_build_invocation_mir_analysis(invocation)?; + + println!( + " - Found {} functions", + results.internal_interface_functions.len() + ); + for func in results.internal_interface_functions.iter().take(10) { + println!(" - {}", func.function_name) + } + if results.internal_interface_functions.len() > 10 { + println!(" ...") + } + } + CompileMode::RunCustomBuild => { + let mut cmd = Command::new(invocation.program) + .args(invocation.args) + .envs(invocation.env) + .current_dir(invocation.cwd) + .spawn()?; + let exit_status = cmd.wait()?; + + if !exit_status.success() { + return Err(anyhow::anyhow!("Failed to run custom build script")); + } + } + } + + // create hardlinks + for (link_name, target) in links { + if fs::metadata(&link_name).is_ok() { + fs::remove_file(&link_name)?; + } + + if fs::metadata(&target).is_err() { + // target does not exist + continue; + } + + if let Err(error) = fs::hard_link(&target, &link_name) { + return Err(anyhow::anyhow!("Failed to create hardlink: {}", error)); + } + } + } + Ok(()) +} + +#[allow(dead_code)] +fn execute_build_invocation_original_rustc(invocation: BuildInvocation) -> anyhow::Result<()> { + // let environment = InvocationEnvironment::enter(&invocation); + let mut command = Command::new(invocation.program) + .args(invocation.args) + .envs(invocation.env) + .current_dir(invocation.cwd) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + + let error = command.wait()?; + if !error.success() { + return Err(anyhow::anyhow!("Failed to compile")); + } + + Ok(()) +} + +fn execute_build_invocation_mir_analysis( + invocation: BuildInvocation, +) -> anyhow::Result { + // print args + let mut args = invocation.args.clone(); + args.insert( + 0, + invocation + .program + .as_os_str() + .to_str() + .ok_or_else(|| anyhow!("Path is not an UTF8-string"))? + .to_owned(), + ); + + let environment = InvocationEnvironment::enter(&invocation); + let mut callbacks = TaintCompilerCallbacks { + package_name: invocation.package_name, + package_version: invocation.package_version, + internal_interface_functions: Vec::default(), + }; + + rustc::run_compiler(args, callbacks.cast_to_dyn())?; + drop(environment); + + Ok(callbacks) +} diff --git a/untrusted_value_taint_checker/src/lib.rs b/untrusted_value_taint_checker/src/lib.rs new file mode 100644 index 0000000..a409769 --- /dev/null +++ b/untrusted_value_taint_checker/src/lib.rs @@ -0,0 +1,11 @@ +#![feature(rustc_private)] + +pub mod cargo; +pub mod rustc; +pub mod analysis { + // pub mod attribute_finder; + pub mod build_plan; + pub mod callbacks; + pub mod invocation_environment; + pub mod taint_source; +} diff --git a/untrusted_value_taint_checker/src/main.rs b/untrusted_value_taint_checker/src/main.rs new file mode 100644 index 0000000..9579e28 --- /dev/null +++ b/untrusted_value_taint_checker/src/main.rs @@ -0,0 +1,115 @@ +#![feature(rustc_private)] + +use std::env; +use std::io::Read; +use std::process::{Command, Stdio}; +use untrusted_value_taint_checker::analysis::build_plan::{BuildPlan, CompileMode}; +use untrusted_value_taint_checker::cargo::{self}; +use untrusted_value_taint_checker::rustc::init_hooks; + +pub fn main() { + init_hooks(); + + let mut cargo_direct_args = vec![]; + let mut rustc_args = vec![]; + + env::args() + .skip(1) + .take_while(|arg| arg != "--") + .filter(|arg| !arg.starts_with("--message-format=")) + .for_each(|arg| { + cargo_direct_args.push(arg); + }); + + env::args() + .skip(1) + .skip_while(|arg| arg != "--") + .skip(1) + .for_each(|arg| { + rustc_args.push(arg); + }); + + let forbidden_args = [ + "--bins", + "--examples", + "--example", + "--tests", + "--test", + "--benches", + "--bench", + "--all-targets", + ]; + for arg in &cargo_direct_args { + if forbidden_args.contains(&arg.as_str()) { + eprintln!("The argument {arg} is invalid for use on the taint checker. To select a target use --lib or --bin {{name}}"); + return; + } + } + + let target_directory = tempfile::tempdir().unwrap(); + let target_directory_string = if let Some(path) = target_directory.path().to_str() { + path + } else { + panic!("Unable to gen temp directory"); + }; + + println!("Target dir {}", target_directory_string); + + let mut command = Command::new("cargo") + .arg("build") + .arg("--message-format=json-render-diagnostics") + .arg("--target-dir") + .arg(target_directory_string) + .arg("--build-plan") + .arg("-Z") + .arg("unstable-options") + .args(&cargo_direct_args) + .arg("--") + .args(&rustc_args) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut reader = std::io::BufReader::new(command.stdout.take().unwrap()); + + let output = command.wait().expect("Couldn't get cargo's exit status"); + if !output.success() { + eprintln!("Compile error. Run cargo to check where the compile error happened."); + return; + } + + let mut build_plan_text = String::default(); + if let Err(e) = reader.read_to_string(&mut build_plan_text) { + eprintln!("Error reading cargo output: {}", e); + return; + } + + let build_plan = build_plan_text + .lines() + .filter(|line| line.contains("invocations")) + .collect::>(); + + if build_plan.len() != 1 { + eprintln!("Error parsing build plan"); + return; + } + + let mut build_plan: BuildPlan = if let Ok(build_plan) = serde_json::from_str(build_plan[0]) { + build_plan + } else { + eprintln!("Error parsing build plan"); + return; + }; + + for invocation in build_plan.invocations.iter_mut() { + if let CompileMode::Build = invocation.compile_mode { + invocation.args.retain(|arg| !arg.starts_with("--json=")); + } + } + + match cargo::execute_build_plan(build_plan) { + Ok(_) => println!("Build succeeded"), + Err(e) => eprintln!("Build failed: {}", e), + } + + drop(target_directory); +} diff --git a/untrusted_value_taint_checker/src/rustc.rs b/untrusted_value_taint_checker/src/rustc.rs new file mode 100644 index 0000000..ab92381 --- /dev/null +++ b/untrusted_value_taint_checker/src/rustc.rs @@ -0,0 +1,107 @@ +extern crate rustc_ast; +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_interface; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_session::{config::ErrorOutputType, EarlyDiagCtxt}; +use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + +pub fn init_hooks() { + let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); + rustc_driver::install_ice_hook("https://github.com/0xCCF4/UntrustedValue/issues", |_| ()); + rustc_driver::init_rustc_env_logger(&early_dcx); + init_tracing(); +} + +fn init_tracing() { + if let Ok(filter) = EnvFilter::try_from_env("TAINT_LOG") { + tracing_subscriber::fmt() + .with_span_events(FmtSpan::ENTER) + .with_env_filter(filter) + .without_time() + .init(); + } +} + +/* +pub struct CwdFileLoader { + cwd: PathBuf, +} + + +impl CwdFileLoader { + pub fn new(cwd: PathBuf) -> Self { + Self { cwd } + } + pub fn transform_path>(&self, path: P) -> PathBuf { + self.cwd.join(path) + } +} + +impl rustc_span::source_map::FileLoader for CwdFileLoader { + fn file_exists(&self, path: &Path) -> bool { + self.transform_path(path).exists() + } + fn read_file(&self, path: &Path) -> std::io::Result { + let mut text = String::default(); + File::open(self.transform_path(path))?.read_to_string(&mut text)?; + Ok(text) + } + fn read_binary_file(&self, path: &Path) -> std::io::Result> { + let mut data = Vec::default(); + File::open(self.transform_path(path))?.read_to_end(&mut data)?; + Ok(Arc::from(data.into_boxed_slice())) + } +} + */ + +pub fn run_compiler( + mut args: Vec, + callbacks: &mut (dyn rustc_driver::Callbacks + Send), +) -> anyhow::Result<()> { + if let Some(sysroot) = compile_time_sysroot() { + let sysroot_flag = "--sysroot"; + if !args.iter().any(|e| e == sysroot_flag) { + args.push(sysroot_flag.to_owned()); + args.push(sysroot); + } + } + + // let cwd_loader: Box<(dyn rustc_span::source_map::FileLoader + Send + std::marker::Sync + 'static)> = Box::new(CwdFileLoader::new(cwd)); + + let exit_code = rustc_driver::catch_with_exit_code(|| { + rustc_driver::RunCompiler::new(&args, callbacks) + //run_compiler.set_file_loader(Some(cwd_loader)); + //.set_using_internal_features(Arc::clone(&using_internal_features)) + .run() + }); + + if exit_code == 0 { + Ok(()) + } else { + Err(anyhow::anyhow!( + "Compiler failed with exit code {}", + exit_code + )) + } +} + +#[allow(clippy::option_env_unwrap)] +fn compile_time_sysroot() -> Option { + if option_env!("RUSTC_STAGE").is_some() { + None + } else { + let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME")); + let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN")); + Some(match (home, toolchain) { + (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain), + _ => option_env!("RUST_SYSROOT") + .expect("To build this without rustup, set the RUST_SYSROOT env var at build time") + .to_owned(), + }) + } +}