From 19c583243aab3936a074d98c0e5d442fb9421bad Mon Sep 17 00:00:00 2001 From: lzoghbi Date: Thu, 25 Jul 2024 13:03:23 -0700 Subject: [PATCH] WIP: added chain auditing functionality in the extension --- editor-plugin/package.json | 5 ++ editor-plugin/src/commands.ts | 21 ++++++++- lang_server/src/notification.rs | 84 ++++++++++++++++++++++++++++++++- lang_server/src/server.rs | 76 +++++++++++++++++++++-------- lang_server/src/util.rs | 26 +++++++++- src/audit_chain.rs | 34 ++++++------- 6 files changed, 206 insertions(+), 40 deletions(-) diff --git a/editor-plugin/package.json b/editor-plugin/package.json index a4819b1..db1bb22 100644 --- a/editor-plugin/package.json +++ b/editor-plugin/package.json @@ -36,6 +36,11 @@ "command": "cargo-scan.create_chain", "title": "Create Chain", "category": "cargo-scan" + }, + { + "command": "cargo-scan.audit_chain", + "title": "Audit Chain", + "category": "cargo-scan" } ], "views": { diff --git a/editor-plugin/src/commands.ts b/editor-plugin/src/commands.ts index 95e79f1..cce4d8a 100644 --- a/editor-plugin/src/commands.ts +++ b/editor-plugin/src/commands.ts @@ -78,7 +78,26 @@ export function registerCommands(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand('cargo-scan.create_chain', async () => { client.sendRequest('cargo-scan.create_chain'); - context.globalState.update('annotateEffects', false); + context.globalState.update('annotateEffects', false); + locationsProvider.clear(); + annotations.clear(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('cargo-scan.audit_chain', async () => { + const response = await client.sendRequest('cargo-scan.audit_chain'); + context.globalState.update('annotateEffects', true); + let effectsMap = new Map(); + + response.effects.forEach(x => { + const location = convertLocation(x[0].location); + effectsMap.set({ ...x[0], location }, x[1]); + }); + + locationsProvider.clear(); + locationsProvider.setLocations([...effectsMap.keys()]); + annotations.setPreviousAnnotations(locationsProvider.getGroupedEffects(), effectsMap); }) ); } diff --git a/lang_server/src/notification.rs b/lang_server/src/notification.rs index d50044d..586e78b 100644 --- a/lang_server/src/notification.rs +++ b/lang_server/src/notification.rs @@ -1,7 +1,18 @@ +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Error}; +use cargo_scan::{ + audit_chain::AuditChain, + audit_file::AuditFile, + ident::CanonicalPath, + scanner::{scan_crate, ScanResults}, +}; use lsp_types::notification::Notification; use serde::{Deserialize, Serialize}; -use crate::request::EffectsResponse; +use crate::{ + location::convert_annotation, request::EffectsResponse, util::find_effect_instance, +}; #[derive(Debug, Deserialize, Serialize)] pub struct AuditNotificationParams { @@ -15,3 +26,74 @@ impl Notification for AuditNotification { type Params = AuditNotificationParams; const METHOD: &'static str = "cargo-scan.set_annotation"; } + +impl AuditNotification { + pub fn annotate_effects_in_single_audit( + params: AuditNotificationParams, + af: &mut AuditFile, + scan_res: &ScanResults, + audit_file_path: PathBuf, + ) -> Result<(), Error> { + let annotation = params.safety_annotation; + let effect = params.effect; + + if let Some(tree) = find_effect_instance(af, effect)? { + let new_ann = convert_annotation(annotation); + tree.set_annotation(new_ann); + af.recalc_pub_caller_checked(&scan_res.pub_fns); + af.version += 1; + + af.save_to_file(audit_file_path)?; + } + + Ok(()) + } + + pub fn annotate_effects_in_chain_audit( + params: AuditNotificationParams, + chain_manifest: &Path, + scan_res: &ScanResults, + root_crate_path: &PathBuf, + ) -> Result<(), Error> { + let annotation = params.safety_annotation; + let effect = params.effect; + + let crate_name = + CanonicalPath::new_owned(effect.get_caller()).crate_name().to_string(); + + if let Some(mut chain) = + AuditChain::read_audit_chain(chain_manifest.to_path_buf())? + { + let crate_id = chain + .resolve_crate_id(&crate_name) + .context(format!("Couldn't resolve crate_name for {}", &crate_name))?; + + if let Some(prev_af) = chain.read_audit_file(&crate_id)? { + let mut new_af = prev_af.clone(); + if let Some(tree) = find_effect_instance(&mut new_af, effect.clone())? { + let new_ann = convert_annotation(annotation); + tree.set_annotation(new_ann); + + if new_af.base_dir != *root_crate_path { + let scan_res = + scan_crate(&new_af.base_dir, &new_af.scanned_effects, true)?; + new_af.recalc_pub_caller_checked(&scan_res.pub_fns); + } else { + new_af.recalc_pub_caller_checked(&scan_res.pub_fns); + }; + + chain.save_audit_file(&crate_id, &new_af)?; + + // update parent crates based off updated effects + let removed_fns = AuditFile::pub_diff(&prev_af, &new_af); + chain + .remove_cross_crate_effects(removed_fns, &chain.root_crate()?)?; + + chain.save_to_file()?; + } + } + } + + Ok(()) + } +} diff --git a/lang_server/src/server.rs b/lang_server/src/server.rs index 6d09901..a2c157f 100644 --- a/lang_server/src/server.rs +++ b/lang_server/src/server.rs @@ -7,8 +7,10 @@ use cargo_scan::{ auditing::chain::{Command, CommandRunner, OuterArgs}, effect::{self}, ident::CanonicalPath, - scanner, + scanner::{self}, + util::load_cargo_toml, }; +use home::home_dir; use log::{debug, info}; use lsp_server::{Connection, Message}; use lsp_types::{ @@ -18,13 +20,16 @@ use lsp_types::{ use serde::{Deserialize, Serialize}; use crate::{ - location::{convert_annotation, to_src_loc}, + location::to_src_loc, notification::{AuditNotification, AuditNotificationParams}, request::{ audit_req, scan_req, AuditCommandResponse, CallerCheckedResponse, EffectsResponse, ScanCommandResponse, }, - util::{add_callers_to_tree, find_effect_instance, get_new_audit_locs}, + util::{ + add_callers_to_tree, find_effect_instance, get_all_chain_effects, + get_new_audit_locs, + }, }; #[derive(Serialize, Deserialize, Debug)] @@ -95,7 +100,7 @@ fn runner( .ok_or_else(|| anyhow!("Couldn't get root path from workspace folders"))?; let root_crate_path = std::path::PathBuf::from_str(root_uri.path())?; - debug!("Crate path received in cargo-scan LSP server: {}", root_crate_path.display()); + info!("Crate path received in cargo-scan LSP server: {}", root_crate_path.display()); let scan_res = scanner::scan_crate(&root_crate_path, effect::DEFAULT_EFFECT_TYPES, false)?; @@ -103,6 +108,7 @@ fn runner( info!("Starting main server loop\n"); let mut audit_file: Option = None; let mut audit_file_path = PathBuf::new(); + let mut chain_manifest = PathBuf::new(); for msg in &conn.receiver { match msg { Message::Request(req) => { @@ -163,16 +169,19 @@ fn runner( } "cargo-scan.create_chain" => { let outer_args = OuterArgs::default(); + let root_crate_id = load_cargo_toml(&root_crate_path)?; + + if let Some(mut dir) = home_dir() { + dir.push(".cargo_audits"); + dir.push("chain_policies"); + dir.push(format!("{}.manifest", root_crate_id)); + + chain_manifest = dir; + }; + let create_args = Create { - crate_path: root_crate_path - .to_str() - .unwrap_or("./") - .to_string(), - manifest_path: root_crate_path - .join("policy.manifest") - .to_str() - .unwrap_or("./policy.manifest") - .to_string(), + crate_path: root_crate_path.to_string_lossy().to_string(), + manifest_path: chain_manifest.to_string_lossy().to_string(), force_overwrite: true, ..Default::default() }; @@ -182,6 +191,27 @@ fn runner( outer_args, )?; } + "cargo-scan.audit_chain" => { + let root_crate_id = load_cargo_toml(&root_crate_path)?; + chain_manifest = home_dir() + .ok_or_else(|| anyhow!("Could not find homw directory"))?; + chain_manifest.push(".cargo_audits"); + chain_manifest.push("chain_policies"); + chain_manifest.push(format!("{}.manifest", root_crate_id)); + + debug!( + "Auditing chain with manifest path: {}", + chain_manifest.display() + ); + let effects = get_all_chain_effects(&chain_manifest)?; + let res = AuditCommandResponse::new(&effects)?.to_json_value()?; + + conn.sender.send(Message::Response(lsp_server::Response { + id: req.id, + result: Some(res), + error: None, + }))?; + } _ => {} }; } @@ -190,15 +220,21 @@ fn runner( if notif.method == AuditNotification::METHOD { let params: AuditNotificationParams = serde_json::from_value(notif.params)?; - let annotation = params.safety_annotation; - let effect = params.effect; if let Some(af) = audit_file.as_mut() { - if let Some(tree) = find_effect_instance(af, effect)? { - let new_ann = convert_annotation(annotation); - tree.set_annotation(new_ann); - af.save_to_file(audit_file_path.clone())?; - } + AuditNotification::annotate_effects_in_single_audit( + params, + af, + &scan_res, + audit_file_path.clone(), + )?; + } else if chain_manifest.is_file() { + AuditNotification::annotate_effects_in_chain_audit( + params, + &chain_manifest, + &scan_res, + &root_crate_path, + )?; } } } diff --git a/lang_server/src/util.rs b/lang_server/src/util.rs index a91e34a..da09175 100644 --- a/lang_server/src/util.rs +++ b/lang_server/src/util.rs @@ -1,6 +1,10 @@ -use anyhow::Error; +use std::{collections::HashMap, path::Path}; + +use anyhow::{anyhow, Error}; use cargo_scan::{ + audit_chain::AuditChain, audit_file::{AuditFile, EffectInfo, EffectTree, SafetyAnnotation}, + effect::EffectInstance, ident::CanonicalPath, scanner::ScanResults, }; @@ -53,3 +57,23 @@ pub fn add_callers_to_tree( *tree = EffectTree::Branch(curr_effect, new_audit_locs); } } + +pub fn get_all_chain_effects( + chain_manifest: &Path, +) -> Result>, Error> { + let mut effects = HashMap::new(); + let mut chain = AuditChain::read_audit_chain(chain_manifest.to_path_buf())? + .ok_or_else(|| { + anyhow!("Couldn't find audit chain manifest at {}", chain_manifest.display()) + })?; + + for crate_id in chain.to_owned().all_crates() { + if let Some(af) = chain.read_audit_file(crate_id)? { + for (effect_instance, audit_tree) in &af.audit_trees { + effects.insert(effect_instance.clone(), audit_tree.get_all_annotations()); + } + } + } + + Ok(effects) +} diff --git a/src/audit_chain.rs b/src/audit_chain.rs index 43c48c0..50e9a4e 100644 --- a/src/audit_chain.rs +++ b/src/audit_chain.rs @@ -19,11 +19,11 @@ use std::str::FromStr; use toml; use crate::audit_file::{AuditFile, AuditVersion, DefaultAuditType}; -use crate::effect::EffectType; -use crate::ident::{CanonicalPath, IdentPath}; +use crate::effect::{EffectType, DEFAULT_EFFECT_TYPES}; +use crate::ident::{replace_hyphens, CanonicalPath, IdentPath}; use crate::util::{load_cargo_toml, CrateId}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct AuditChain { #[serde(skip)] manifest_path: PathBuf, @@ -127,7 +127,9 @@ impl AuditChain { pub fn resolve_all_crates(&self, search_name: &str) -> Vec { let mut res = Vec::new(); for (crate_id, _) in self.crate_policies.iter() { - if crate_id.crate_name == search_name { + let mut crate_name = crate_id.crate_name.clone(); + replace_hyphens(&mut crate_name); + if crate_name == search_name || crate_id.crate_name == search_name { res.push(crate_id.clone()); } } @@ -338,25 +340,23 @@ impl Create { impl Default for Create { fn default() -> Self { + let audit_path = home::home_dir() + .map(|mut dir| { + dir.push(".cargo_audits"); + dir + }) + .unwrap_or_else(|| PathBuf::from(".audit_files")) + .to_string_lossy() + .to_string(); + Self { crate_path: ".".to_string(), manifest_path: "./policy.manifest".to_string(), - audit_path: ".audit_files".to_string(), + audit_path, force_overwrite: false, download_root_crate: None, download_version: None, - effect_types: [ - EffectType::SinkCall, - EffectType::FFICall, - EffectType::UnsafeCall, - EffectType::RawPointer, - EffectType::UnionField, - EffectType::StaticMut, - EffectType::StaticExt, - EffectType::FnPtrCreation, - EffectType::ClosureCreation, - ] - .to_vec(), + effect_types: DEFAULT_EFFECT_TYPES.to_vec(), } } }