mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-16 08:46:48 +00:00
add cargo-component
This commit is contained in:
parent
c172373b54
commit
f6f87ec1e5
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
|
||||
|
6
Makefile
6
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
|
||||
|
||||
|
10
README.md
10
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
|
||||
|
@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2022-08-01"
|
||||
channel = "nightly-2023-02-05"
|
||||
|
@ -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
10
src/Cargo.lock
generated
@ -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",
|
||||
|
@ -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
10
src/Components.toml
Normal 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
|
@ -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`.
|
@ -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};
|
||||
|
3
src/services/comp-sys/cargo-component/.gitignore
vendored
Normal file
3
src/services/comp-sys/cargo-component/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
analysis/target
|
||||
analysis/Cargo.lock
|
46
src/services/comp-sys/cargo-component/Cargo.lock
generated
Normal file
46
src/services/comp-sys/cargo-component/Cargo.lock
generated
Normal 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",
|
||||
]
|
27
src/services/comp-sys/cargo-component/Cargo.toml
Normal file
27
src/services/comp-sys/cargo-component/Cargo.toml
Normal 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
|
19
src/services/comp-sys/cargo-component/README.md
Normal file
19
src/services/comp-sys/cargo-component/README.md
Normal 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.
|
12
src/services/comp-sys/cargo-component/analysis/Cargo.toml
Normal file
12
src/services/comp-sys/cargo-component/analysis/Cargo.toml
Normal 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"
|
296
src/services/comp-sys/cargo-component/analysis/src/conf.rs
Normal file
296
src/services/comp-sys/cargo-component/analysis/src/conf.rs
Normal 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();
|
||||
}
|
275
src/services/comp-sys/cargo-component/analysis/src/lib.rs
Normal file
275
src/services/comp-sys/cargo-component/analysis/src/lib.rs
Normal 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();
|
||||
}
|
||||
}
|
12
src/services/comp-sys/cargo-component/build.rs
Normal file
12
src/services/comp-sys/cargo-component/build.rs
Normal 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!();
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2023-02-05"
|
247
src/services/comp-sys/cargo-component/src/driver.rs
Normal file
247
src/services/comp-sys/cargo-component/src/driver.rs
Normal 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()
|
||||
}
|
||||
}))
|
||||
}
|
132
src/services/comp-sys/cargo-component/src/main.rs
Normal file
132
src/services/comp-sys/cargo-component/src/main.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
@ -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]
|
@ -0,0 +1,5 @@
|
||||
[components]
|
||||
foo = { name = "foo" }
|
||||
bar = { name = "foo" }
|
||||
|
||||
[whitelist]
|
@ -0,0 +1,3 @@
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
23
src/services/comp-sys/cargo-component/tests/missing_toml.rs
Normal file
23
src/services/comp-sys/cargo-component/tests/missing_toml.rs
Normal 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"));
|
||||
}
|
@ -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]
|
@ -0,0 +1,3 @@
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
23
src/services/comp-sys/cargo-component/tests/regression.rs
Normal file
23
src/services/comp-sys/cargo-component/tests/regression.rs
Normal 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);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
[workspace]
|
||||
members = ["foo", "bar"]
|
@ -0,0 +1,10 @@
|
||||
[components]
|
||||
foo = { name = "foo" }
|
||||
bar = { name = "bar" }
|
||||
|
||||
[whitelist]
|
||||
[whitelist.foo.foo_add]
|
||||
bar = true
|
||||
|
||||
[whitelist.foo.FOO_ITEM]
|
||||
bar = true
|
@ -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"}
|
@ -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)
|
||||
}
|
@ -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"}
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
[workspace]
|
||||
members = ["foo", "bar"]
|
@ -0,0 +1,5 @@
|
||||
[components]
|
||||
foo = { name = "foo" }
|
||||
bar = { name = "bar" }
|
||||
|
||||
[whitelist]
|
@ -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"}
|
@ -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)
|
||||
}
|
@ -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"}
|
@ -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
|
||||
}
|
13
src/services/comp-sys/controlled/Cargo.toml
Normal file
13
src/services/comp-sys/controlled/Cargo.toml
Normal 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"
|
55
src/services/comp-sys/controlled/src/lib.rs
Normal file
55
src/services/comp-sys/controlled/src/lib.rs
Normal 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
|
||||
}
|
@ -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"}
|
||||
|
@ -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!()
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user