diff --git a/.gitignore b/.gitignore index a24ace96e..5ecbcc3fb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +# Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Makefile b/Makefile index eaec018f8..5f583fe97 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ -.PHONY: all build clean docs fmt run setup test +.PHONY: all build clean docs fmt run setup test tools all: build test setup: @rustup component add rust-src + @rustup component add rustc-dev @rustup component add llvm-tools-preview @cargo install mdbook @@ -11,6 +12,9 @@ build: @cd src && cargo kbuild @cd src && cargo kimage +tools: + @cd src/services/comp-sys && cargo install --path cargo-component + run: build @cd src && cargo krun diff --git a/README.md b/README.md index dbd77bcf9..754827d9e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,16 @@ all developmennt tools are installed. make setup ``` +Then, install some standalone tools (e.g., `cargo-component`) under the project directory. +``` bash +make tools +``` + +Set environmental variables to enable `cargo` find installed tools. +```bash +export PATH=`pwd`/src/target/bin:${PATH} +``` + Then, we can build and test the project. ```bash make diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 725abbe38..1f6f0ea95 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2022-08-01" \ No newline at end of file +channel = "nightly-2023-02-05" diff --git a/src/.cargo/config.toml b/src/.cargo/config.toml index d692eefbf..ac3b67b29 100644 --- a/src/.cargo/config.toml +++ b/src/.cargo/config.toml @@ -7,4 +7,9 @@ kcheck = "check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_buil kbuild = "build --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" kimage = "run --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -- --no-run" krun = "run --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" -ktest = "test --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" \ No newline at end of file +ktest = "test --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" +component-check = "component check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" + +[install] +root = "./target" + diff --git a/src/Cargo.lock b/src/Cargo.lock index 37d09fe8c..07b88b8e4 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -90,6 +90,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "controlled" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.8.0" @@ -186,6 +195,7 @@ version = "0.1.0" dependencies = [ "ascii", "bitflags", + "controlled", "jinux-frame", "jinux-pci", "jinux-rights-proc", diff --git a/src/Cargo.toml b/src/Cargo.toml index 7b1a1b491..5c1fad321 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -24,6 +24,11 @@ members = [ "services/libs/jinux-util", ] +exclude = [ + "services/comp-sys/controlled", + "services/comp-sys/cargo-component", +] + [package.metadata.bootloader] map-physical-memory = true physical-memory-offset = "0xFFFF800000000000" diff --git a/src/Components.toml b/src/Components.toml new file mode 100644 index 000000000..76498fc93 --- /dev/null +++ b/src/Components.toml @@ -0,0 +1,10 @@ +# template +[components] +std = { name = "jinux-std" } +pci = { name = "jinux-pci" } +virtio = { name = "jinux-virtio"} +main = { name = "jinux" } + +[whitelist] +[whitelist.std.run_first_process] +main = true diff --git a/src/apps/busybox/README.md b/src/apps/busybox/README.md index 687043adf..06c61a01e 100644 --- a/src/apps/busybox/README.md +++ b/src/apps/busybox/README.md @@ -4,4 +4,4 @@ We don't include the source code of busybox here since the source code is really After download the source code of busybox 1.35.0 and unzip, then cd to the directory of busybox 1. `make defconfig`. We set all config as default. 2. Set static link option in .config: `CONFIG_STATIC=y`. We need a static-linked busybox binary since we does not support dynamic linking now. -3. Set standalone shell option in .config: `CONFIG_FEATURE_SH_STANDALONE=y`. The standalone ash will try to call busybox applets instead of search binaries in host system. e.g., when running ls, standalone ash will invoke `busybox ash`. \ No newline at end of file +3. Set standalone shell option in .config: `CONFIG_FEATURE_SH_STANDALONE=y`. The standalone ash will try to call busybox applets instead of search binaries in host system. e.g., when running ls, standalone ash will invoke `busybox ls`. \ No newline at end of file diff --git a/src/framework/jinux-frame/src/mm/memory_set.rs b/src/framework/jinux-frame/src/mm/memory_set.rs index c7038ee2b..a00cf9273 100644 --- a/src/framework/jinux-frame/src/mm/memory_set.rs +++ b/src/framework/jinux-frame/src/mm/memory_set.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use crate::{ config::PAGE_SIZE, mm::address::is_aligned, - vm::{VmFrame, VmFrameVec, VmPerm}, + vm::{VmFrame, VmFrameVec}, *, }; use alloc::collections::{btree_map::Entry, BTreeMap}; diff --git a/src/services/comp-sys/cargo-component/.gitignore b/src/services/comp-sys/cargo-component/.gitignore new file mode 100644 index 000000000..2ca38dcc4 --- /dev/null +++ b/src/services/comp-sys/cargo-component/.gitignore @@ -0,0 +1,3 @@ +/target +analysis/target +analysis/Cargo.lock diff --git a/src/services/comp-sys/cargo-component/Cargo.lock b/src/services/comp-sys/cargo-component/Cargo.lock new file mode 100644 index 000000000..997169195 --- /dev/null +++ b/src/services/comp-sys/cargo-component/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "analysis" +version = "0.1.0" +dependencies = [ + "once_cell", + "toml", +] + +[[package]] +name = "cargo-component" +version = "0.1.0" +dependencies = [ + "analysis", + "rustc_tools_util", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "rustc_tools_util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba09476327c4b70ccefb6180f046ef588c26a24cf5d269a9feba316eb4f029f" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] diff --git a/src/services/comp-sys/cargo-component/Cargo.toml b/src/services/comp-sys/cargo-component/Cargo.toml new file mode 100644 index 000000000..726f904c8 --- /dev/null +++ b/src/services/comp-sys/cargo-component/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cargo-component" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "cargo-component" +test = false +path = "src/main.rs" + +[[bin]] +name = "component-driver" +path = "src/driver.rs" + +[dependencies] +rustc_tools_util = "0.3.0" +analysis = { path = "analysis" } + +[build-dependencies] +rustc_tools_util = "0.3.0" + +[package.metadata.rust-analyzer] +# This package uses #[feature(rustc_private)] +rustc_private = true \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/README.md b/src/services/comp-sys/cargo-component/README.md new file mode 100644 index 000000000..82af27a7e --- /dev/null +++ b/src/services/comp-sys/cargo-component/README.md @@ -0,0 +1,19 @@ +## Overview +The crate contains cargo-component, a cargo subcommand to enable component-level access control in Jinux. For more info about Jinux component system, see the [RFC](https://github.com/jinzhao-dev/jinux/issues/58). The implementation mainly follows [rust clippy](https://github.com/rust-lang/rust-clippy). Internally, this tool will call `cargo check` to compile the whole project and bases the analysis on MIR. + +## install +After running `make setup` for jinux, this crate can be created with cargo. +```shell +cargo install --path . +``` +This will install two binaries `cargo-component` and `component-driver` at `$HOME/.cargo/bin`(by default, it depends on the cargo config). + +## Usage +Use `cargo component` or `cargo component check` or `cargo component audit`. The three commands are the same now. For jinux, we shoud use another alias command `cargo component-check`, which was defined in `src/.cargo/config.toml`. + +### Two notes: +- The directory **where you run the command** should contains a `Components.toml` config file, where defines all components and whitelist. +- The project checked by cargo-component should use the same rust-toolchain as cargo-component, which was defined in rust-toolchain.toml. + +## Known limitations +This tool uses rustc private APIs, which is highly unstable. So if the rust toolchain is updated, the tool may need updates too. \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/analysis/Cargo.toml b/src/services/comp-sys/cargo-component/analysis/Cargo.toml new file mode 100644 index 000000000..0f12ce9dc --- /dev/null +++ b/src/services/comp-sys/cargo-component/analysis/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "analysis" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[workspace] + +[dependencies] +toml = "0.5.10" +once_cell = "1.17.0" \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/analysis/src/conf.rs b/src/services/comp-sys/cargo-component/analysis/src/conf.rs new file mode 100644 index 000000000..707b2ea89 --- /dev/null +++ b/src/services/comp-sys/cargo-component/analysis/src/conf.rs @@ -0,0 +1,296 @@ +use std::collections::{BTreeMap, HashSet}; +use std::{env, fs, io, path::PathBuf}; + +use once_cell::sync::OnceCell; +use toml::Value; + +pub static CONFIG: OnceCell = OnceCell::new(); + +#[derive(Debug, Clone)] +pub struct Config { + components: BTreeMap, + whitelists: BTreeMap, +} + +impl Config { + pub fn parse_toml(config_toml: Value) -> Self { + let components_value = config_toml + .get("components") + .expect("The `components` key does not exist"); + let components = parse_components(components_value); + let whitelist_value = config_toml + .get("whitelist") + .expect("The `whitelist` key does not exist"); + let whitelists = parse_whitelists(whitelist_value); + Config { + components, + whitelists, + } + } + + pub fn ident_full_path(&self, ident: &Ident) -> Path { + let component_ident = ident.iter().nth(0).unwrap(); + let component_path = self + .components + .get(component_ident) + .expect("Undefined component ident") + .clone(); + let component_libname = component_path.iter().last().unwrap(); + let mut ident_path = ident.clone(); + ident_path.remove_segment(0); + ident_path.insert_segment(0, component_libname.clone()); + ident_path + } + + pub fn component_path(&self, component_ident: &str) -> ComponentName { + self.components + .get(component_ident) + .expect("Undefinded component name") + .clone() + } + + pub fn allow_access(&self, crate_name: &str, def_path: &str) -> bool { + let def_path = Path::from_qualified_str(def_path); + for (ident, white_list) in &self.whitelists { + let ident_full_path = self.ident_full_path(ident); + if def_path == ident_full_path { + for (component_ident, allowed) in white_list.iter() { + let component_lib_name = self.component_path(component_ident).filename(); + if crate_name == &component_lib_name { + return *allowed; + } + } + } + } + false + } + + /// ensure the config to be valid. We will check three things. + /// 1. The component ident and library name(The last segment of component path) cannot be duplicate. + /// 2. The controlled type in whilelist should be in one of defined components. + /// 3. The components in whilelist should be defined. + pub fn check_config(&self) { + let mut component_idents = HashSet::new(); + let mut lib_names = HashSet::new(); + + // check 1 + for (ident, component_path) in &self.components { + if component_idents.contains(ident) { + panic!("duplicate component ident"); + } + component_idents.insert(ident.to_string()); + let lib_name = component_path.filename(); + if lib_names.contains(&lib_name) { + panic!("duplicate library names"); + } + lib_names.insert(lib_name); + } + + for (type_, whilelist) in &self.whitelists { + // check 2 + let component_ident = type_.iter().nth(0).unwrap(); + if !component_idents.contains(component_ident) { + panic!("The controlled type is not in any component."); + } + // check 3 + for (component_name, _) in whilelist.iter() { + if !component_idents.contains(component_name) { + panic!("The component in whitelist is not defined"); + } + } + } + } +} + +#[derive(Debug, Clone)] +pub struct WhiteList { + components: BTreeMap, +} + +impl WhiteList { + pub fn new() -> Self { + WhiteList { + components: BTreeMap::new(), + } + } + + pub fn add(&mut self, component_name: &str, allowed: bool) { + assert!(!self.components.contains_key(component_name)); + self.components.insert(component_name.to_string(), allowed); + } + + pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, bool> { + self.components.iter() + } +} + +// rust crate name does not allow '-', so when we store ,all '-' will be replaced with '_' +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Path { + segments: Vec, +} + +impl Path { + pub fn from_str(path: &str) -> Self { + let segments = path + .split("/") + .filter(|segment| segment.len() > 0) + .map(|segment| segment.to_string().replace("-", "_")) + .collect(); + Self { segments } + } + + pub fn from_qualified_str(qualified_path: &str) -> Self { + let segments = qualified_path + .split("::") + .filter(|segment| segment.len() > 0) + .map(|segment| segment.to_string().replace("-", "_")) + .collect(); + Self { segments } + } + + pub fn from_segments(segments: Vec) -> Self { + let segments = segments + .into_iter() + .map(|segment| segment.replace("-", "_")) + .collect(); + Self { segments } + } + + pub fn iter(&self) -> std::slice::Iter { + self.segments.iter() + } + + pub fn remove_segment(&mut self, index: usize) { + self.segments.remove(index); + } + + pub fn insert_segment(&mut self, index: usize, segment: String) { + self.segments.insert(index, segment); + } + + pub fn filename(&self) -> String { + self.segments.iter().last().unwrap().clone() + } +} + +type Ident = Path; +type ComponentName = Path; + +fn parse_components(components_value: &Value) -> BTreeMap { + let mut components = BTreeMap::new(); + if let Value::Table(components_map) = components_value { + for (ident, component_table) in components_map { + let name_value = component_table + .get("name") + .expect("the `name` key does not exist."); + if let Value::String(path) = name_value { + let component_path = ComponentName::from_str(path); + components.insert(ident.clone(), component_path); + } + } + return components; + } else { + unreachable!("`components` should be a table") + } +} + +fn parse_whitelists(whitelist_value: &Value) -> BTreeMap { + let mut recorded_path = Vec::new(); + let mut whitelists = BTreeMap::new(); + if let Value::Table(whitelist_map) = whitelist_value { + for (key, value) in whitelist_map { + parse_whitelist_item(key, value, &mut recorded_path, &mut whitelists) + } + return whitelists; + } else { + unreachable!("whitelist should be a table") + } +} + +fn parse_whitelist_item( + key: &str, + value: &Value, + recorded_path: &mut Vec, + whitelists: &mut BTreeMap, +) { + match value { + Value::Boolean(allowed) => { + let type_ = Ident::from_segments(recorded_path.clone()); + if whitelists.contains_key(&type_) { + let white_list: &mut WhiteList = whitelists.get_mut(&type_).unwrap(); + white_list.add(key, *allowed); + } else { + let mut white_list = WhiteList::new(); + white_list.add(key, *allowed); + whitelists.insert(type_, white_list); + } + } + Value::Table(table) => { + recorded_path.push(key.to_string()); + for (inner_key, inner_value) in table { + parse_whitelist_item(inner_key, inner_value, recorded_path, whitelists); + } + recorded_path.pop(); + } + _ => { + unreachable!() + } + } +} + +/// Search for the configuration file. +/// +/// # Errors +/// +/// Returns any unexpected filesystem error encountered when searching for the config file +pub fn lookup_conf_file() -> io::Result> { + /// Possible filename to search for. + const CONFIG_FILE_NAMES: [&str; 4] = [ + "Components.toml", + ".Components.toml", + "components.toml", + ".components.toml", + ]; + + // Start looking for a config file in COMPONENT_CONFIG_DIR.(This should be the directory execute cargo component) + let current = PathBuf::from(env::var_os("COMPONENT_CONFIG_DIR").unwrap()); + let mut found_config: Option = None; + + loop { + for config_file_name in &CONFIG_FILE_NAMES { + if let Ok(config_file) = current.join(config_file_name).canonicalize() { + match fs::metadata(&config_file) { + Err(e) if e.kind() == io::ErrorKind::NotFound => {} + Err(e) => return Err(e), + Ok(md) if md.is_dir() => {} + Ok(_) => { + if let Some(ref found_config_) = found_config { + eprintln!( + "Using config file `{}`\nWarning: `{}` will be ignored.", + found_config_.display(), + config_file.display(), + ); + } else { + found_config = Some(config_file); + } + } + } + } + } + + if found_config.is_some() { + return Ok(found_config); + } + + return Ok(None); + } +} + +pub fn init(conf_path: &str) { + let file_content = std::fs::read_to_string(conf_path).expect("Read config file failed"); + let config_toml = file_content.parse::().unwrap(); + let config = Config::parse_toml(config_toml); + config.check_config(); + CONFIG.set(config).unwrap(); +} diff --git a/src/services/comp-sys/cargo-component/analysis/src/lib.rs b/src/services/comp-sys/cargo-component/analysis/src/lib.rs new file mode 100644 index 000000000..5d10f2d8d --- /dev/null +++ b/src/services/comp-sys/cargo-component/analysis/src/lib.rs @@ -0,0 +1,275 @@ +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_driver; +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; + +mod conf; + +use std::collections::HashSet; + +pub use conf::init as init_conf; +pub use conf::lookup_conf_file; + +use rustc_ast::AttrKind; +use rustc_middle::mir::{ + Constant, InlineAsmOperand, LocalDecl, Operand, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, +}; +use rustc_middle::ty::{InstanceDef, TyCtxt, TyKind, WithOptConstParam}; +use rustc_span::def_id::{DefId, LocalDefId, LOCAL_CRATE}; +use rustc_span::Span; + +const TOOL_NAME: &'static str = "component_access_control"; +const CONTROLLED_ATTR: &'static str = "controlled"; + +pub fn enter_analysis<'tcx>(tcx: TyCtxt<'tcx>) { + for mir_key in tcx.mir_keys(()) { + check_body_mir(mir_key.clone(), tcx) + } +} + +fn check_body_mir(mir_key: LocalDefId, tcx: TyCtxt<'_>) { + let def_id = WithOptConstParam::unknown(mir_key.to_def_id()); + // For const function/block, instance_mir returns mir_for_ctfe. + // For normal function, instance_mir returns optimized_mir. + let body = tcx.instance_mir(InstanceDef::Item(def_id)); + + let mut checked_def_ids = HashSet::new(); + for basic_block_data in body.basic_blocks.iter() { + // This check based on the assumption that any **DIRECT** visit to + // static variables or functions can be found in Operand. + // FIXME: is this true? + for statement in &basic_block_data.statements { + check_statement(statement, tcx, &mut checked_def_ids); + } + + if let Some(terminator) = &basic_block_data.terminator { + check_terminator(terminator, tcx, &mut checked_def_ids); + } + } + + // For some special cases, assign a function to a function pointer may not exist in statements, + // while a local decl with type of the function exist. So we further check each local decl to + // avoid missing any entry points. + for local_decl in body.local_decls.iter() { + check_local_decl(local_decl, tcx, &checked_def_ids) + } +} + +fn check_statement(statement: &Statement, tcx: TyCtxt<'_>, checked_def_ids: &mut HashSet) { + // FIXME: operand only exist in assign statement? + let mut def_paths = Vec::new(); + if let StatementKind::Assign(assignment) = &statement.kind { + let rvalue = &assignment.1; + match rvalue { + Rvalue::Use(operand) + | Rvalue::Repeat(operand, _) + | Rvalue::Cast(_, operand, _) + | Rvalue::UnaryOp(_, operand) + | Rvalue::ShallowInitBox(operand, _) => { + check_invalid_operand(operand, tcx, &mut def_paths, checked_def_ids); + } + Rvalue::BinaryOp(_, two_operands) | Rvalue::CheckedBinaryOp(_, two_operands) => { + check_invalid_operand(&two_operands.0, tcx, &mut def_paths, checked_def_ids); + check_invalid_operand(&two_operands.1, tcx, &mut def_paths, checked_def_ids); + } + Rvalue::Aggregate(_, operands) => { + for operand in operands { + check_invalid_operand(operand, tcx, &mut def_paths, checked_def_ids); + } + } + _ => {} + } + } + let crate_symbol = tcx.crate_name(LOCAL_CRATE); + let crate_name = crate_symbol.as_str(); + emit_note(tcx, statement.source_info.span, crate_name, def_paths) +} + +fn check_terminator( + terminator: &Terminator, + tcx: TyCtxt<'_>, + checked_def_ids: &mut HashSet, +) { + let mut def_paths = Vec::new(); + match &terminator.kind { + TerminatorKind::SwitchInt { discr: operand, .. } + | TerminatorKind::DropAndReplace { value: operand, .. } + | TerminatorKind::Assert { cond: operand, .. } + | TerminatorKind::Yield { value: operand, .. } => { + check_invalid_operand(operand, tcx, &mut def_paths, checked_def_ids); + } + TerminatorKind::Call { func, args, .. } => { + check_invalid_operand(func, tcx, &mut def_paths, checked_def_ids); + for arg in args { + check_invalid_operand(arg, tcx, &mut def_paths, checked_def_ids); + } + } + TerminatorKind::InlineAsm { operands, .. } => { + for asm_operand in operands { + check_inline_asm_operand(&asm_operand, tcx, &mut def_paths, checked_def_ids); + } + } + _ => {} + } + let crate_symbol = tcx.crate_name(LOCAL_CRATE); + let crate_name = crate_symbol.as_str(); + emit_note(tcx, terminator.source_info.span, crate_name, def_paths) +} + +fn check_local_decl(local_decl: &LocalDecl<'_>, tcx: TyCtxt<'_>, checked_def_ids: &HashSet) { + let ty = local_decl.ty; + let def_id = if let TyKind::FnDef(def_id, ..) = ty.kind() { + // func def + *def_id + } else { + return; + }; + if checked_def_ids.contains(&def_id) { + return; + } + let crate_symbol = tcx.crate_name(LOCAL_CRATE); + let crate_name = crate_symbol.as_str(); + if let Some(def_path) = def_path_if_invalid_access(def_id, tcx) { + emit_note(tcx, local_decl.source_info.span, crate_name, vec![def_path]); + } +} + +fn check_inline_asm_operand( + asm_operand: &InlineAsmOperand<'_>, + tcx: TyCtxt<'_>, + def_paths: &mut Vec, + checked_def_ids: &mut HashSet, +) { + match asm_operand { + InlineAsmOperand::In { value: operand, .. } + | InlineAsmOperand::InOut { + in_value: operand, .. + } => { + check_invalid_operand(operand, tcx, def_paths, checked_def_ids); + } + InlineAsmOperand::Const { value } | InlineAsmOperand::SymFn { value } => { + check_constant(value, tcx, def_paths, checked_def_ids); + } + _ => {} + } +} + +/// check whether visiting the operand in local crate is valid. +/// if the operand is invalid, add the def_path to def_paths. +/// The operand is invalid only when follwing four points are all satisfied. +/// 1. The operand represents a static variable or a func(the first argument can not be self or its variants). +/// 2. The operand is not defined in local crate. +/// 3. The operand is marked with #[component_access_control::controlled] +/// 4. Local crate is not in the whitelist to visit the operand. +fn check_invalid_operand( + operand: &Operand, + tcx: TyCtxt<'_>, + def_paths: &mut Vec, + checked_def_ids: &mut HashSet, +) { + if let Operand::Constant(constant) = operand { + check_constant(&constant, tcx, def_paths, checked_def_ids); + } else { + return; + }; +} + +fn check_constant( + constant: &Constant<'_>, + tcx: TyCtxt<'_>, + def_paths: &mut Vec, + checked_def_ids: &mut HashSet, +) { + // get def_id of Constant and func + let def_id = if let Some(def_id) = constant.check_static_ptr(tcx) { + // static variable + def_id + } else { + let ty = constant.ty(); + if let TyKind::FnDef(def_id, ..) = ty.kind() { + // func def + *def_id + } else { + return; + } + }; + checked_def_ids.insert(def_id); + + if let Some(def_path) = def_path_if_invalid_access(def_id, tcx) { + def_paths.push(def_path); + } +} + +fn def_path_if_invalid_access(def_id: DefId, tcx: TyCtxt<'_>) -> Option { + if def_id.is_local() { + return None; + } + if !contains_controlled_attr(def_id, tcx) { + return None; + } + def_path_if_not_in_whitelist(def_id, tcx) +} + +/// check whether the def_id is in white list. +/// If the def_id is **NOT** in white list, return the def_path +fn def_path_if_not_in_whitelist(def_id: DefId, tcx: TyCtxt<'_>) -> Option { + let crate_symbol = tcx.crate_name(LOCAL_CRATE); + let crate_name = crate_symbol.as_str(); + let def_path_str = def_path_str(def_id, tcx); + if conf::CONFIG + .get() + .unwrap() + .allow_access(crate_name, &def_path_str) + { + None + } else { + Some(def_path_str) + } +} + +fn def_path_str(def_id: DefId, tcx: TyCtxt<'_>) -> String { + // The def_path_str of TyCtxt will panic the compiler, + // while the def_path_debug_str contains noisy info. + // This function is like def_path_debug_str. + let def_path = tcx.def_path(def_id); + let crate_name = tcx.crate_name(def_path.krate); + format!("{}{}", crate_name, def_path.to_string_no_crate_verbose()) +} + +/// if the def_id has attribute component_access_control::controlled, return true, else return false +fn contains_controlled_attr(def_id: DefId, tcx: TyCtxt<'_>) -> bool { + for attr in tcx.get_attrs_unchecked(def_id) { + if let AttrKind::Normal(normal_attr) = &attr.kind { + let path_segments = &normal_attr.item.path.segments; + if path_segments.len() != 2 { + return false; + } + let segment_strs: Vec<_> = path_segments + .iter() + .map(|segment| segment.ident.as_str()) + .collect(); + if segment_strs[0] == TOOL_NAME && segment_strs[1] == CONTROLLED_ATTR { + return true; + } + } + } + false +} + +fn emit_note(tcx: TyCtxt<'_>, span: Span, crate_name: &str, def_paths: Vec) { + if def_paths.len() > 0 { + let sess = tcx.sess; + const TITLE: &'static str = "access controlled entry point is disallowed"; + let def_path = def_paths.join(", "); + let warning_message = format!("access {} in {}", def_path, crate_name); + sess.struct_span_warn(span, TITLE) + .note(warning_message) + .emit(); + } +} diff --git a/src/services/comp-sys/cargo-component/build.rs b/src/services/comp-sys/cargo-component/build.rs new file mode 100644 index 000000000..fab8a69a2 --- /dev/null +++ b/src/services/comp-sys/cargo-component/build.rs @@ -0,0 +1,12 @@ +//! This implementation is from rust-clippy + +fn main() { + // Forward the profile to the main compilation + println!( + "cargo:rustc-env=PROFILE={}", + std::env::var("PROFILE").unwrap() + ); + // Don't rebuild even if nothing changed + println!("cargo:rerun-if-changed=build.rs"); + rustc_tools_util::setup_version_info!(); +} diff --git a/src/services/comp-sys/cargo-component/rust-toolchain.toml b/src/services/comp-sys/cargo-component/rust-toolchain.toml new file mode 100644 index 000000000..1f6f0ea95 --- /dev/null +++ b/src/services/comp-sys/cargo-component/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2023-02-05" diff --git a/src/services/comp-sys/cargo-component/src/driver.rs b/src/services/comp-sys/cargo-component/src/driver.rs new file mode 100644 index 000000000..4a378237b --- /dev/null +++ b/src/services/comp-sys/cargo-component/src/driver.rs @@ -0,0 +1,247 @@ +//! Licensed under the Apache License, Version 2.0 or the MIT License. +//! Copyright (C) 2023 Ant Group. + +//! This implementation is from rust clippy. We modified the code. +#![feature(rustc_private)] +#![feature(once_cell)] + +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_interface; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_driver::Compilation; +use rustc_interface::interface; +use rustc_session::parse::ParseSess; +use rustc_span::symbol::Symbol; + +use std::borrow::Cow; +use std::env; +use std::ops::Deref; +use std::panic; +use std::path::Path; +use std::process::exit; +use std::sync::LazyLock; + +/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If +/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`. +fn arg_value<'a, T: Deref>( + args: &'a [T], + find_arg: &str, + pred: impl Fn(&str) -> bool, +) -> Option<&'a str> { + let mut args = args.iter().map(Deref::deref); + while let Some(arg) = args.next() { + let mut arg = arg.splitn(2, '='); + if arg.next() != Some(find_arg) { + continue; + } + + match arg.next().or_else(|| args.next()) { + Some(v) if pred(v) => return Some(v), + _ => {} + } + } + None +} + +/// Track files that may be accessed at runtime in `file_depinfo` so that cargo will re-run component-driver +/// when any of them are modified +fn track_files(parse_sess: &mut ParseSess, conf_path_string: Option) { + let file_depinfo = parse_sess.file_depinfo.get_mut(); + + // `cargo component` executes `component-driver` + // with the current directory set to `CARGO_MANIFEST_DIR` so a relative path is fine + if Path::new("Cargo.toml").exists() { + file_depinfo.insert(Symbol::intern("Cargo.toml")); + } + + // `Components.toml` + if let Some(path) = conf_path_string { + file_depinfo.insert(Symbol::intern(&path)); + } + + // During development track the `component-driver` executable so that cargo will re-run component whenever + // it is rebuilt + if cfg!(debug_assertions) { + if let Ok(current_exe) = env::current_exe() { + if let Some(current_exe) = current_exe.to_str() { + file_depinfo.insert(Symbol::intern(current_exe)); + } + } + } +} + +struct DefaultCallbacks; +impl rustc_driver::Callbacks for DefaultCallbacks {} + +struct ComponentCallbacks; +impl rustc_driver::Callbacks for ComponentCallbacks { + // JUSTIFICATION: necessary to set `mir_opt_level` + #[allow(rustc::bad_opt_access)] + fn config(&mut self, config: &mut interface::Config) { + let conf_path = analysis::lookup_conf_file(); + let conf_path_string = if let Ok(Some(path)) = &conf_path { + path.to_str().map(String::from) + } else { + None + }; + + if let Some(ref conf_path) = conf_path_string { + analysis::init_conf(&conf_path); + } else { + panic!("cannot find components.toml"); + } + + config.parse_sess_created = Some(Box::new(move |parse_sess| { + track_files(parse_sess, conf_path_string); + })); + // Avoid optimization + config.opts.unstable_opts.mir_opt_level = Some(0); + } + + fn after_analysis<'tcx>( + &mut self, + _: &rustc_interface::interface::Compiler, + queries: &'tcx rustc_interface::Queries<'tcx>, + ) -> Compilation { + queries.global_ctxt().unwrap().enter(|tcx| { + tcx.sess.abort_if_errors(); + analysis::enter_analysis(tcx); + tcx.sess.abort_if_errors(); + }); + Compilation::Continue + } +} + +fn display_help() { + println!( + "\ +Checks whether a package violates access control policy. +Usage: + cargo component [options] +Common options: + audit + check + " + ); +} + +type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static; +static ICE_HOOK: LazyLock> = LazyLock::new(|| { + let hook = panic::take_hook(); + panic::set_hook(Box::new(|info| report_ice(info))); + hook +}); + +fn report_ice(info: &panic::PanicInfo<'_>) { + // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace + (*ICE_HOOK)(info); + + // Separate the output with an empty line + eprintln!(); + + let fallback_bundle = + rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( + rustc_errors::ColorConfig::Auto, + None, + None, + fallback_bundle, + false, + false, + None, + false, + false, + )); + let handler = rustc_errors::Handler::with_emitter(true, None, emitter); + + // a .span_bug or .bug call has already printed what + // it wants to print. + if !info.payload().is::() { + let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic"); + handler.emit_diagnostic(&mut d); + } + + let xs: Vec> = vec!["the compiler unexpectedly panicked. ".into()]; + + for note in &xs { + handler.note_without_error(note.as_ref()); + } + + // If backtraces are enabled, also print the query stack + let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0"); + + let num_frames = if backtrace { None } else { Some(2) }; + + interface::try_print_query_stack(&handler, num_frames); +} + +#[allow(clippy::too_many_lines)] +pub fn main() { + rustc_driver::init_rustc_env_logger(); + LazyLock::force(&ICE_HOOK); + exit(rustc_driver::catch_with_exit_code(move || { + let mut orig_args: Vec = env::args().collect(); + let has_sysroot_arg = arg_value(&orig_args, "--sysroot", |_| true).is_some(); + + let sys_root_env = std::env::var("SYSROOT").ok(); + let pass_sysroot_env_if_given = |args: &mut Vec, sys_root_env| { + if let Some(sys_root) = sys_root_env { + if !has_sysroot_arg { + args.extend(vec!["--sysroot".into(), sys_root]); + } + }; + }; + + // make "component-driver --rustc" work like a subcommand that passes further args to "rustc" + // for example `component-driver --rustc --version` will print the rustc version that component-driver + // uses + if let Some(pos) = orig_args.iter().position(|arg| arg == "--rustc") { + orig_args.remove(pos); + orig_args[0] = "rustc".to_string(); + + let mut args: Vec = orig_args.clone(); + pass_sysroot_env_if_given(&mut args, sys_root_env); + + return rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run(); + } + + if orig_args.iter().any(|a| a == "--version" || a == "-V") { + let version_info = rustc_tools_util::get_version_info!(); + println!("{version_info}"); + exit(0); + } + + // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. + // We're invoking the compiler programmatically, so we ignore this/ + let wrapper_mode = + orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref()); + + if wrapper_mode { + // we still want to be able to invoke it normally though + orig_args.remove(1); + } + + if !wrapper_mode + && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) + { + display_help(); + exit(0); + } + + let mut args: Vec = orig_args.clone(); + pass_sysroot_env_if_given(&mut args, sys_root_env); + + let no_deps = false; + let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); + + let component_enabled = !no_deps || in_primary_package; + if component_enabled { + rustc_driver::RunCompiler::new(&args, &mut ComponentCallbacks).run() + } else { + rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run() + } + })) +} diff --git a/src/services/comp-sys/cargo-component/src/main.rs b/src/services/comp-sys/cargo-component/src/main.rs new file mode 100644 index 000000000..9f7feace1 --- /dev/null +++ b/src/services/comp-sys/cargo-component/src/main.rs @@ -0,0 +1,132 @@ +//! Licensed under the Apache License, Version 2.0 or the MIT License. +//! Copyright (C) 2023 Ant Group. + +//! This implementation is from rust clippy. We modified the code. + +use std::env; +use std::path::PathBuf; +use std::process::{self, Command}; + +const CARGO_COMPONENT_HELP: &str = r#"Checks whether a package violates access control policy. +Usage: + cargo component [options] +Common options: + audit + check +"#; + +fn show_help() { + println!("{CARGO_COMPONENT_HELP}"); +} + +fn show_version() { + let version_info = rustc_tools_util::get_version_info!(); + println!("{version_info}"); +} + +pub fn main() { + // Check for version and help flags even when invoked as 'cargo-component' + if env::args().any(|a| a == "--help" || a == "-h") { + show_help(); + return; + } + + if env::args().any(|a| a == "--version" || a == "-V") { + show_version(); + return; + } + + if let Err(code) = process(env::args().skip(2)) { + process::exit(code); + } +} + +struct ComponentCmd { + cargo_subcommand: &'static str, + args: Vec, + component_args: Vec, +} + +impl ComponentCmd { + fn new(mut old_args: I) -> Self + where + I: Iterator, + { + let cargo_subcommand = "check"; + let mut args = vec![]; + let mut component_args: Vec = vec![]; + + for arg in old_args.by_ref() { + match arg.as_str() { + "check" => { + component_args.push("check".into()); + continue; + } + "audit" => { + component_args.push("audit".into()); + continue; + } + "--" => break, + _ => {} + } + args.push(arg); + } + + component_args.append(&mut (old_args.collect())); + + Self { + cargo_subcommand, + args, + component_args, + } + } + + fn path() -> PathBuf { + let mut path = env::current_exe() + .expect("current executable path invalid") + .with_file_name("component-driver"); + + if cfg!(windows) { + path.set_extension("exe"); + } + + path + } + + fn into_std_cmd(self) -> Command { + let mut cmd = Command::new("cargo"); + let component_args: String = self + .component_args + .iter() + .map(|arg| format!("{arg}")) + .collect(); + cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path()) + .env("COMPONENT_ARGS", component_args) + .env("COMPONENT_CONFIG_DIR", std::env::current_dir().unwrap()) + .arg(self.cargo_subcommand) + .args(&self.args); + + cmd + } +} + +fn process(old_args: I) -> Result<(), i32> +where + I: Iterator, +{ + let cmd = ComponentCmd::new(old_args); + + let mut cmd = cmd.into_std_cmd(); + + let exit_status = cmd + .spawn() + .expect("could not run cargo") + .wait() + .expect("failed to wait for cargo?"); + + if exit_status.success() { + Ok(()) + } else { + Err(exit_status.code().unwrap_or(-1)) + } +} diff --git a/src/services/comp-sys/cargo-component/tests/duplicate_lib_name.rs b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name.rs new file mode 100644 index 000000000..3de5ecbcc --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name.rs @@ -0,0 +1,23 @@ +//! This test checks that if two components have same name, the compiler will panic. + +#![feature(once_cell)] + +use std::path::PathBuf; +use test_utils::{cargo_clean, cargo_component}; +mod test_utils; + +#[test] +fn duplicate_lib_name() { + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = root_dir.join("target").join("duplicate_lib_name_test"); + let cwd = root_dir.join("tests").join("duplicate_lib_name_test"); + let output = cargo_clean(&cwd, &target_dir); + assert!(output.status.success()); + + let output = cargo_component(&cwd, &target_dir); + let stderr = String::from_utf8_lossy(&output.stderr); + println!("stderr: {stderr}"); + + assert!(!output.status.success()); + assert!(stderr.contains("duplicate library names")); +} diff --git a/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/Cargo.toml b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/Cargo.toml new file mode 100644 index 000000000..17190f6e5 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "duplicate_lib_name_test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/Components.toml b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/Components.toml new file mode 100644 index 000000000..e409c920c --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/Components.toml @@ -0,0 +1,5 @@ +[components] +foo = { name = "foo" } +bar = { name = "foo" } + +[whitelist] diff --git a/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/src/lib.rs b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/src/lib.rs new file mode 100644 index 000000000..211bfff7e --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/duplicate_lib_name_test/src/lib.rs @@ -0,0 +1,3 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} diff --git a/src/services/comp-sys/cargo-component/tests/missing_toml.rs b/src/services/comp-sys/cargo-component/tests/missing_toml.rs new file mode 100644 index 000000000..855aab3dd --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/missing_toml.rs @@ -0,0 +1,23 @@ +//! This test checks that if Components.toml is missed, the compiler will panic. + +#![feature(once_cell)] + +use std::path::PathBuf; +use test_utils::{cargo_clean, cargo_component}; +mod test_utils; + +#[test] +fn missing_toml() { + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = root_dir.join("target").join("missing_toml_test"); + let cwd = root_dir.join("tests").join("missing_toml_test"); + let output = cargo_clean(&cwd, &target_dir); + assert!(output.status.success()); + + let output = cargo_component(&cwd, &target_dir); + let stderr = String::from_utf8_lossy(&output.stderr); + println!("stderr: {stderr}"); + + assert!(!output.status.success()); + assert!(stderr.contains("cannot find components.toml")); +} diff --git a/src/services/comp-sys/cargo-component/tests/missing_toml_test/Cargo.toml b/src/services/comp-sys/cargo-component/tests/missing_toml_test/Cargo.toml new file mode 100644 index 000000000..e2901d8ad --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/missing_toml_test/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "missing_toml_test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/services/comp-sys/cargo-component/tests/missing_toml_test/src/lib.rs b/src/services/comp-sys/cargo-component/tests/missing_toml_test/src/lib.rs new file mode 100644 index 000000000..211bfff7e --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/missing_toml_test/src/lib.rs @@ -0,0 +1,3 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} diff --git a/src/services/comp-sys/cargo-component/tests/regression.rs b/src/services/comp-sys/cargo-component/tests/regression.rs new file mode 100644 index 000000000..4c3558976 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression.rs @@ -0,0 +1,23 @@ +//! This test checks that visiting controlled resources in whitelist is allowed. + +#![feature(once_cell)] + +use std::path::PathBuf; +use test_utils::{cargo_clean, cargo_component, clean_after_test}; +mod test_utils; + +#[test] +fn regression() { + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = root_dir.join("target").join("regression_test"); + let cwd = root_dir.join("tests").join("regression_test"); + let output = cargo_clean(&cwd, &target_dir); + assert!(output.status.success()); + + let output = cargo_component(&cwd, &target_dir); + let stderr = String::from_utf8_lossy(&output.stderr); + println!("stderr: {stderr}"); + + assert!(output.status.success()); + clean_after_test(&cwd); +} diff --git a/src/services/comp-sys/cargo-component/tests/regression_test/Cargo.toml b/src/services/comp-sys/cargo-component/tests/regression_test/Cargo.toml new file mode 100644 index 000000000..7492eaa8e --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression_test/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["foo", "bar"] \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/tests/regression_test/Components.toml b/src/services/comp-sys/cargo-component/tests/regression_test/Components.toml new file mode 100644 index 000000000..7b02d66c3 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression_test/Components.toml @@ -0,0 +1,10 @@ +[components] +foo = { name = "foo" } +bar = { name = "bar" } + +[whitelist] +[whitelist.foo.foo_add] +bar = true + +[whitelist.foo.FOO_ITEM] +bar = true \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/tests/regression_test/bar/Cargo.toml b/src/services/comp-sys/cargo-component/tests/regression_test/bar/Cargo.toml new file mode 100644 index 000000000..0978ddf95 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression_test/bar/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bar" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +foo = {path = "../foo"} diff --git a/src/services/comp-sys/cargo-component/tests/regression_test/bar/src/lib.rs b/src/services/comp-sys/cargo-component/tests/regression_test/bar/src/lib.rs new file mode 100644 index 000000000..1a28ac90e --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression_test/bar/src/lib.rs @@ -0,0 +1,5 @@ +pub static BAR: &'static usize = &foo::FOO_ITEM; + +pub fn add(left: usize, right: usize) -> usize { + foo::foo_add(left, right) +} diff --git a/src/services/comp-sys/cargo-component/tests/regression_test/foo/Cargo.toml b/src/services/comp-sys/cargo-component/tests/regression_test/foo/Cargo.toml new file mode 100644 index 000000000..6c9d38eb0 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression_test/foo/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +controlled = {path = "../../../../controlled"} diff --git a/src/services/comp-sys/cargo-component/tests/regression_test/foo/src/lib.rs b/src/services/comp-sys/cargo-component/tests/regression_test/foo/src/lib.rs new file mode 100644 index 000000000..08896719a --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/regression_test/foo/src/lib.rs @@ -0,0 +1,13 @@ +#![feature(register_tool)] +#![register_tool(component_access_control)] + +#[macro_use] +extern crate controlled; + +#[controlled] +pub static FOO_ITEM: usize = 0; + +#[controlled] +pub fn foo_add(left: usize, right: usize) -> usize { + left + right +} diff --git a/src/services/comp-sys/cargo-component/tests/test_utils/mod.rs b/src/services/comp-sys/cargo-component/tests/test_utils/mod.rs new file mode 100644 index 000000000..3147a334c --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/test_utils/mod.rs @@ -0,0 +1,36 @@ +#![allow(unused)] + +use std::path::PathBuf; +use std::process::Command; +use std::process::Output; +use std::sync::LazyLock; + +pub static CARGO_COMPONENT_PATH: LazyLock = LazyLock::new(|| { + let mut path = std::env::current_exe().unwrap(); + assert!(path.pop()); // deps + path.set_file_name("cargo-component"); + path +}); + +pub fn cargo_clean(cwd: &PathBuf, target_dir: &PathBuf) -> Output { + Command::new("cargo") + .arg("clean") + .current_dir(cwd) + .env("CARGO_TARGET_DIR", target_dir) + .output() + .unwrap() +} + +pub fn cargo_component(cwd: &PathBuf, target_dir: &PathBuf) -> Output { + Command::new(&*CARGO_COMPONENT_PATH) + .current_dir(cwd) + .env("CARGO_INCREMENTAL", "0") + .env("CARGO_TARGET_DIR", target_dir) + .output() + .unwrap() +} + +pub fn clean_after_test(cwd: &PathBuf) { + let cargo_lock = cwd.join("Cargo.lock"); + std::fs::remove_file(cargo_lock); +} diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy.rs b/src/services/comp-sys/cargo-component/tests/violate_policy.rs new file mode 100644 index 000000000..f4e48768a --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy.rs @@ -0,0 +1,26 @@ +//! This test checks that if controlled resource not in whitelist is visited, cargo-component will report warning message. + +#![feature(once_cell)] + +use std::path::PathBuf; +use test_utils::{cargo_clean, cargo_component, clean_after_test}; +mod test_utils; + +#[test] +fn violate_policy() { + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = root_dir.join("target").join("violate_policy_test"); + let cwd = root_dir.join("tests").join("violate_policy_test"); + let output = cargo_clean(&cwd, &target_dir); + assert!(output.status.success()); + + let output = cargo_component(&cwd, &target_dir); + let stderr = String::from_utf8_lossy(&output.stderr); + println!("stderr: {stderr}"); + + assert!(output.status.success()); + assert!(stderr.contains("access controlled entry point is disallowed")); + assert!(stderr.contains("access foo::foo_add in bar")); + assert!(stderr.contains("access foo::FOO_ITEM in bar")); + clean_after_test(&cwd); +} diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy_test/Cargo.toml b/src/services/comp-sys/cargo-component/tests/violate_policy_test/Cargo.toml new file mode 100644 index 000000000..7492eaa8e --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy_test/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["foo", "bar"] \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy_test/Components.toml b/src/services/comp-sys/cargo-component/tests/violate_policy_test/Components.toml new file mode 100644 index 000000000..f63e85247 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy_test/Components.toml @@ -0,0 +1,5 @@ +[components] +foo = { name = "foo" } +bar = { name = "bar" } + +[whitelist] \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy_test/bar/Cargo.toml b/src/services/comp-sys/cargo-component/tests/violate_policy_test/bar/Cargo.toml new file mode 100644 index 000000000..0978ddf95 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy_test/bar/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bar" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +foo = {path = "../foo"} diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy_test/bar/src/lib.rs b/src/services/comp-sys/cargo-component/tests/violate_policy_test/bar/src/lib.rs new file mode 100644 index 000000000..1b4a642a4 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy_test/bar/src/lib.rs @@ -0,0 +1,5 @@ +pub static BAR: &'static usize = &foo::FOO_ITEM; + +pub fn add(left: usize, right: usize) -> usize { + foo::foo_add(left, right) +} \ No newline at end of file diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy_test/foo/Cargo.toml b/src/services/comp-sys/cargo-component/tests/violate_policy_test/foo/Cargo.toml new file mode 100644 index 000000000..6c9d38eb0 --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy_test/foo/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +controlled = {path = "../../../../controlled"} diff --git a/src/services/comp-sys/cargo-component/tests/violate_policy_test/foo/src/lib.rs b/src/services/comp-sys/cargo-component/tests/violate_policy_test/foo/src/lib.rs new file mode 100644 index 000000000..08896719a --- /dev/null +++ b/src/services/comp-sys/cargo-component/tests/violate_policy_test/foo/src/lib.rs @@ -0,0 +1,13 @@ +#![feature(register_tool)] +#![register_tool(component_access_control)] + +#[macro_use] +extern crate controlled; + +#[controlled] +pub static FOO_ITEM: usize = 0; + +#[controlled] +pub fn foo_add(left: usize, right: usize) -> usize { + left + right +} diff --git a/src/services/comp-sys/controlled/Cargo.toml b/src/services/comp-sys/controlled/Cargo.toml new file mode 100644 index 000000000..52657c981 --- /dev/null +++ b/src/services/comp-sys/controlled/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "controlled" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "1.0.90" diff --git a/src/services/comp-sys/controlled/src/lib.rs b/src/services/comp-sys/controlled/src/lib.rs new file mode 100644 index 000000000..a047e8b07 --- /dev/null +++ b/src/services/comp-sys/controlled/src/lib.rs @@ -0,0 +1,55 @@ +//! This crate defines two attribute macros `controlled` and `uncontrolled`. +//! This two macros are attached to functions or static variables to enable crate level access control. +//! To use these two macros, a crate must at first registers a tool named `component_access_control`, +//! because controlled used tool attribute internally. +//! +//! Below is a simple usage example. +//! ```rust +//! // crate-level tool registration +//! #![feature(register_tool)] +//! #![register_tool(component_access_control)] +//! +//! #[macro_use] +//! extern crate controlled; // import this crate +//! +//! #[controlled] +//! pub static FOO: usize = 0; +//! +//! #[uncontrolled] +//! pub fn bar() {} +//! ``` +use quote::quote; + +#[proc_macro_attribute] +pub fn controlled( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let attr = attr.to_string(); + if attr.len() != 0 { + panic!("controlled cannot accept inner tokens.") + } + let mut tokens: proc_macro::TokenStream = quote!( + #[component_access_control::controlled] + ) + .into(); + tokens.extend(item); + tokens +} + +#[proc_macro_attribute] +pub fn uncontrolled( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let attr = attr.to_string(); + if attr.len() != 0 { + panic!("uncontrolled cannot accept inner tokens.") + } + let mut tokens: proc_macro::TokenStream = quote!( + #[component_access_control::uncontrolled] + ) + .into(); + tokens.extend(item); + tokens +} diff --git a/src/services/libs/jinux-std/Cargo.toml b/src/services/libs/jinux-std/Cargo.toml index f1a8b7f65..05d46871f 100644 --- a/src/services/libs/jinux-std/Cargo.toml +++ b/src/services/libs/jinux-std/Cargo.toml @@ -11,6 +11,7 @@ pod = {path = "../../../framework/pod"} pod-derive = {path = "../../../framework/pod-derive"} jinux-pci = {path="../../comps/jinux-pci"} jinux-virtio = {path="../../comps/jinux-virtio"} +controlled = { path = "../../comp-sys/controlled" } typeflags = {path="../typeflags"} typeflags-util = {path="../typeflags-util"} jinux-rights-proc = {path="../jinux-rights-proc"} diff --git a/src/services/libs/jinux-std/src/lib.rs b/src/services/libs/jinux-std/src/lib.rs index 8f4f17af7..6088d441f 100644 --- a/src/services/libs/jinux-std/src/lib.rs +++ b/src/services/libs/jinux-std/src/lib.rs @@ -2,10 +2,9 @@ #![no_std] #![forbid(unsafe_code)] #![allow(dead_code)] +#![allow(incomplete_features)] #![allow(unused_variables)] -#![feature(const_btree_new)] #![feature(cstr_from_bytes_until_nul)] -#![feature(half_open_range_patterns)] #![feature(exclusive_range_pattern)] #![feature(btree_drain_filter)] #![feature(const_option)] @@ -16,6 +15,8 @@ #![feature(specialization)] #![feature(fn_traits)] #![feature(linked_list_remove)] +#![feature(register_tool)] +#![register_tool(component_access_control)] use crate::{ prelude::*, @@ -30,6 +31,8 @@ use crate::process::{ extern crate alloc; extern crate lru; +#[macro_use] +extern crate controlled; pub mod driver; pub mod error; @@ -106,6 +109,7 @@ pub fn init_thread() { } /// first process never return +#[controlled] pub fn run_first_process() -> ! { Process::spawn_kernel_process(init_thread); unreachable!() diff --git a/src/services/libs/jinux-std/src/vm/vmo/mod.rs b/src/services/libs/jinux-std/src/vm/vmo/mod.rs index d3a954431..71a9baecf 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/mod.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/mod.rs @@ -415,7 +415,6 @@ impl Vmo_ { self.decommit(new_size..old_size)?; self.inner.lock().size = new_size; } else { - self.commit(old_size..new_size)?; self.inner.lock().size = new_size; }