add cargo-component

This commit is contained in:
Jianfeng Jiang 2023-02-07 16:05:21 +08:00
parent c172373b54
commit f6f87ec1e5
48 changed files with 1445 additions and 9 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2022-08-01"
channel = "nightly-2023-02-05"

View File

@ -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"
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"

10
src/Cargo.lock generated
View File

@ -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",

View File

@ -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"

10
src/Components.toml Normal file
View File

@ -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

View File

@ -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`.
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`.

View File

@ -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};

View File

@ -0,0 +1,3 @@
/target
analysis/target
analysis/Cargo.lock

View File

@ -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",
]

View File

@ -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

View File

@ -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.

View File

@ -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"

View File

@ -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<Config> = OnceCell::new();
#[derive(Debug, Clone)]
pub struct Config {
components: BTreeMap<String, ComponentName>,
whitelists: BTreeMap<Ident, WhiteList>,
}
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<String, bool>,
}
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<String>,
}
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<String>) -> Self {
let segments = segments
.into_iter()
.map(|segment| segment.replace("-", "_"))
.collect();
Self { segments }
}
pub fn iter(&self) -> std::slice::Iter<String> {
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<String, Path> {
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<Ident, WhiteList> {
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<String>,
whitelists: &mut BTreeMap<Ident, WhiteList>,
) {
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<Option<PathBuf>> {
/// 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<PathBuf> = 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::<Value>().unwrap();
let config = Config::parse_toml(config_toml);
config.check_config();
CONFIG.set(config).unwrap();
}

View File

@ -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<DefId>) {
// 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<DefId>,
) {
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<DefId>) {
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<String>,
checked_def_ids: &mut HashSet<DefId>,
) {
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<String>,
checked_def_ids: &mut HashSet<DefId>,
) {
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<String>,
checked_def_ids: &mut HashSet<DefId>,
) {
// 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<String> {
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<String> {
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<String>) {
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();
}
}

View File

@ -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!();
}

View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2023-02-05"

View File

@ -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<Target = str>>(
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<String>) {
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<Box<PanicCallback>> = 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::<rustc_errors::ExplicitBug>() {
let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic");
handler.emit_diagnostic(&mut d);
}
let xs: Vec<Cow<'static, str>> = 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<String> = 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<String>, 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<String> = 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<String> = 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()
}
}))
}

View File

@ -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<String>,
component_args: Vec<String>,
}
impl ComponentCmd {
fn new<I>(mut old_args: I) -> Self
where
I: Iterator<Item = String>,
{
let cargo_subcommand = "check";
let mut args = vec![];
let mut component_args: Vec<String> = 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<I>(old_args: I) -> Result<(), i32>
where
I: Iterator<Item = String>,
{
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))
}
}

View File

@ -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"));
}

View File

@ -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]

View File

@ -0,0 +1,5 @@
[components]
foo = { name = "foo" }
bar = { name = "foo" }
[whitelist]

View File

@ -0,0 +1,3 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}

View File

@ -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"));
}

View File

@ -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]

View File

@ -0,0 +1,3 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}

View File

@ -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);
}

View File

@ -0,0 +1,2 @@
[workspace]
members = ["foo", "bar"]

View File

@ -0,0 +1,10 @@
[components]
foo = { name = "foo" }
bar = { name = "bar" }
[whitelist]
[whitelist.foo.foo_add]
bar = true
[whitelist.foo.FOO_ITEM]
bar = true

View File

@ -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"}

View File

@ -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)
}

View File

@ -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"}

View File

@ -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
}

View File

@ -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<PathBuf> = 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);
}

View File

@ -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);
}

View File

@ -0,0 +1,2 @@
[workspace]
members = ["foo", "bar"]

View File

@ -0,0 +1,5 @@
[components]
foo = { name = "foo" }
bar = { name = "bar" }
[whitelist]

View File

@ -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"}

View File

@ -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)
}

View File

@ -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"}

View File

@ -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
}

View File

@ -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"

View File

@ -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
}

View File

@ -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"}

View File

@ -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!()

View File

@ -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;
}