diff --git a/Cargo.lock b/Cargo.lock index ee10e5c824..241d34356d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,7 @@ dependencies = [ "base", "bit_field", "broker_ipc", + "cc", "cfg-if", "cros_async", "crosvm_plugin", diff --git a/Cargo.toml b/Cargo.toml index 849187ef24..755d0a7b61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,6 +230,9 @@ tube_transporter = { path = "tube_transporter" } winapi = "*" win_util = { path = "win_util"} +[build-dependencies] +cc = "*" + [dev-dependencies] base = "*" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..59bd41f41d --- /dev/null +++ b/build.rs @@ -0,0 +1,156 @@ +// Copyright 2022 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::env; +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; + +fn generate_preprocessed(minijail_dir: &Path, out_dir: &Path) { + let env_cc = cc::Build::new() + .get_compiler() + .path() + .as_os_str() + .to_owned(); + + Command::new(minijail_dir.join("gen_constants.sh")) + .env("CC", &env_cc) + .env("SRC", &minijail_dir) + .arg(out_dir.join("libconstants.gen.c")) + .spawn() + .unwrap() + .wait() + .expect("Generate kernel constant table failed"); + + Command::new(minijail_dir.join("gen_syscalls.sh")) + .env("CC", &env_cc) + .env("SRC", &minijail_dir) + .arg(out_dir.join("libsyscalls.gen.c")) + .spawn() + .unwrap() + .wait() + .expect("Generate syscall table failed"); +} + +fn generate_llvm_ir(minijail_dir: &Path, out_dir: &Path, target: &str) { + Command::new("clang") + .arg("-target") + .arg(target) + .arg("-S") + .arg("-emit-llvm") + .arg("-I") + .arg(minijail_dir) + .arg(out_dir.join("libconstants.gen.c")) + .arg(out_dir.join("libsyscalls.gen.c")) + .current_dir(&out_dir) + .spawn() + .unwrap() + .wait() + .expect("Convert kernel constants and syscalls to llvm ir failed"); +} + +fn generate_constants_json(minijail_dir: &Path, out_dir: &Path) { + Command::new(minijail_dir.join("tools/generate_constants_json.py")) + .arg("--output") + .arg(out_dir.join("constants.json")) + .arg(out_dir.join("libconstants.gen.ll")) + .arg(out_dir.join("libsyscalls.gen.ll")) + .spawn() + .unwrap() + .wait() + .expect("Generate constants.json failed"); +} + +fn rewrite_policies(seccomp_policy_path: &Path, rewrote_policy_folder: &Path) { + for entry in fs::read_dir(seccomp_policy_path).unwrap() { + let policy_file = entry.unwrap(); + let policy_file_content = fs::read_to_string(policy_file.path()).unwrap(); + let policy_file_content_rewrote = + policy_file_content.replace("/usr/share/policy/crosvm", "."); + fs::write( + rewrote_policy_folder.join(policy_file.file_name()), + policy_file_content_rewrote, + ) + .unwrap(); + } +} + +fn compile_policies(out_dir: &Path, rewrote_policy_folder: &Path, minijail_dir: &Path) { + let compiled_policy_folder = out_dir.join("policy_output"); + fs::create_dir_all(&compiled_policy_folder).unwrap(); + let mut include_all_bytes = String::from("std::collections::HashMap::from([\n"); + for entry in fs::read_dir(&rewrote_policy_folder).unwrap() { + let policy_file = entry.unwrap(); + if policy_file.path().extension().unwrap() == "policy" { + let output_file_path = compiled_policy_folder.join( + policy_file + .path() + .with_extension("bpf") + .file_name() + .unwrap(), + ); + Command::new(minijail_dir.join("tools/compile_seccomp_policy.py")) + .arg("--arch-json") + .arg(out_dir.join("constants.json")) + .arg("--default-action") + .arg("trap") + .arg(policy_file.path()) + .arg(&output_file_path) + .spawn() + .unwrap() + .wait() + .expect("Compile bpf failed"); + let s = format!( + r#"("{}", include_bytes!("{}").to_vec()),"#, + policy_file.path().file_stem().unwrap().to_str().unwrap(), + output_file_path.to_str().unwrap() + ); + include_all_bytes += s.as_str(); + } + } + include_all_bytes += "])"; + fs::write(out_dir.join("bpf_includes.in"), include_all_bytes).unwrap(); +} + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=seccomp"); + + if env::var("CARGO_CFG_TARGET_FAMILY").unwrap() != "unix" + || env::var("CARGO_FEATURE_CHROMEOS").is_ok() + { + return; + } + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let minijail_dir = if let Ok(minijail_dir_env) = env::var("MINIJAIL_DIR") { + PathBuf::from(minijail_dir_env) + } else { + src_dir.join("third_party/minijail") + }; + + let target = env::var("TARGET").unwrap(); + + generate_preprocessed(&minijail_dir, &out_dir); + generate_llvm_ir(&minijail_dir, &out_dir, &target); + generate_constants_json(&minijail_dir, &out_dir); + + // check policies exist for target architecuture + let seccomp_arch_name = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { + "armv7" => "arm".to_owned(), + x => x.to_owned(), + }; + let seccomp_policy_path = src_dir.join("seccomp").join(&seccomp_arch_name); + assert!( + seccomp_policy_path.is_dir(), + "Seccomp policy dir doesn't exist" + ); + + let rewrote_policy_folder = out_dir.join("policy_input"); + fs::create_dir_all(&rewrote_policy_folder).unwrap(); + rewrite_policies(&seccomp_policy_path, &rewrote_policy_folder); + compile_policies(&out_dir, &rewrote_policy_folder, &minijail_dir); +} diff --git a/docs/book/src/building_crosvm.md b/docs/book/src/building_crosvm.md index bca19b3ebd..8ee0d95554 100644 --- a/docs/book/src/building_crosvm.md +++ b/docs/book/src/building_crosvm.md @@ -172,19 +172,13 @@ The `--quick` variant will skip some slower checks, like building for other plat ## Known issues -- By default, crosvm is running devices in sandboxed mode, which requires seccomp policy files to be - set up. For local testing it is often easier to `--disable-sandbox` to run everything in a single - process. - If your Linux header files are too old, you may find minijail rejecting seccomp filters for - containing unknown syscalls. You can try removing the offending lines from the filter file, or add - `--seccomp-log-failures` to the crosvm command line to turn these into warnings. Note that this - option will also stop minijail from killing processes that violate the seccomp rule, making the - sandboxing much less aggressive. -- Seccomp policy files have hardcoded absolute paths. You can either fix up the paths locally, or - set up an awesome hacky symlink: - `sudo mkdir /usr/share/policy && sudo ln -s /path/to/crosvm/seccomp/x86_64 /usr/share/policy/crosvm`. - We'll eventually build the precompiled policies - [into the crosvm binary](http://crbug.com/1052126). + containing unknown syscalls. You can try removing the offending lines from the filter file and + recompile or add `--seccomp-log-failures` to the crosvm command line to turn these into warnings. + Using this option also requires you to specify path to seccomp policiy source files with + `--seccomp-policy-dir` and adhere to (or modify) the hardcoded absolute include paths in them. + Note that this option will also stop minijail from killing processes that violate the seccomp + rule, making the sandboxing much less aggressive. - Devices can't be jailed if `/var/empty` doesn't exist. `sudo mkdir -p /var/empty` to work around this for now. - You need read/write permissions for `/dev/kvm` to run tests or other crosvm instances. Usually diff --git a/integration_tests/tests/fixture/mod.rs b/integration_tests/tests/fixture/mod.rs index 8feadf372c..3f8b7545e4 100644 --- a/integration_tests/tests/fixture/mod.rs +++ b/integration_tests/tests/fixture/mod.rs @@ -309,7 +309,7 @@ impl TestVm { let control_socket_path = test_dir.path().join("control"); let mut command = Command::new(find_crosvm_binary()); - command.args(&["run", "--disable-sandbox"]); + command.args(&["run"]); TestVm::configure_serial_devices(&mut command, &from_guest_pipe, &to_guest_pipe); command.args(&["--socket", control_socket_path.to_str().unwrap()]); TestVm::configure_rootfs(&mut command, cfg.o_direct); diff --git a/src/crosvm/config.rs b/src/crosvm/config.rs index b1e01faa87..57769fa0ce 100644 --- a/src/crosvm/config.rs +++ b/src/crosvm/config.rs @@ -63,6 +63,7 @@ cfg_if::cfg_if! { static KVM_PATH: &str = "/dev/kvm"; static VHOST_NET_PATH: &str = "/dev/vhost-net"; + #[cfg(feature="chromeos")] static SECCOMP_POLICY_DIR: &str = "/usr/share/policy/crosvm"; } else if #[cfg(windows)] { use base::{Event, Tube}; @@ -533,18 +534,12 @@ fn jail_config_default_pivot_root() -> PathBuf { PathBuf::from(option_env!("DEFAULT_PIVOT_ROOT").unwrap_or("/var/empty")) } -#[cfg(unix)] -fn jail_config_default_seccomp_policy_dir() -> Option { - Some(PathBuf::from(SECCOMP_POLICY_DIR)) -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, serde_keyvalue::FromKeyValues)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct JailConfig { #[serde(default = "jail_config_default_pivot_root")] pub pivot_root: PathBuf, #[cfg(unix)] - #[serde(default = "jail_config_default_seccomp_policy_dir")] pub seccomp_policy_dir: Option, #[serde(default)] pub seccomp_log_failures: bool, @@ -554,8 +549,10 @@ impl Default for JailConfig { fn default() -> Self { JailConfig { pivot_root: jail_config_default_pivot_root(), - #[cfg(unix)] - seccomp_policy_dir: jail_config_default_seccomp_policy_dir(), + #[cfg(feature = "chromeos")] + seccomp_policy_dir: Some(PathBuf::from(SECCOMP_POLICY_DIR)), + #[cfg(all(unix, not(feature = "chromeos")))] + seccomp_policy_dir: None, seccomp_log_failures: false, } } @@ -2170,7 +2167,7 @@ mod tests { JailConfig { pivot_root: jail_config_default_pivot_root(), #[cfg(unix)] - seccomp_policy_dir: jail_config_default_seccomp_policy_dir(), + seccomp_policy_dir: None, seccomp_log_failures: false, } ); diff --git a/src/crosvm/sys/unix/jail_helpers.rs b/src/crosvm/sys/unix/jail_helpers.rs index fe884c9d0d..0e8d5fdc09 100644 --- a/src/crosvm/sys/unix/jail_helpers.rs +++ b/src/crosvm/sys/unix/jail_helpers.rs @@ -13,10 +13,21 @@ use libc::c_ulong; use libc::gid_t; use libc::uid_t; use minijail::Minijail; +use once_cell::sync::Lazy; use crate::crosvm::config::JailConfig; -#[allow(dead_code)] +static EMBEDDED_BPFS: Lazy>> = Lazy::new(|| { + #[cfg(not(feature = "chromeos"))] + { + include!(concat!(env!("OUT_DIR"), "/bpf_includes.in")) + } + #[cfg(feature = "chromeos")] + { + std::collections::HashMap::<&str, Vec>::new() + } +}); + pub(super) struct SandboxConfig<'a> { pub(super) limit_caps: bool, pub(super) log_failures: bool, @@ -67,27 +78,55 @@ pub(super) fn create_base_minijail( // Don't allow the device to gain new privileges. j.no_new_privs(); - // By default we'll prioritize using the pre-compiled .bpf over the .policy - // file (the .bpf is expected to be compiled using "trap" as the failure - // behavior instead of the default "kill" behavior). - // Refer to the code comment for the "seccomp-log-failures" - // command-line parameter for an explanation about why the |log_failures| - // flag forces the use of .policy files (and the build-time alternative to - // this run-time flag). - let bpf_policy_file = config.seccomp_policy_path.unwrap().with_extension("bpf"); - if bpf_policy_file.exists() && !config.log_failures { - j.parse_seccomp_program(&bpf_policy_file) - .context("failed to parse precompiled seccomp policy")?; - } else { - // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP, - // which will correctly kill the entire device process if a worker - // thread commits a seccomp violation. - j.set_seccomp_filter_tsync(); - if config.log_failures { - j.log_seccomp_filter_failures(); + if let Some(seccomp_policy_path) = config.seccomp_policy_path { + // By default we'll prioritize using the pre-compiled .bpf over the + // .policy file (the .bpf is expected to be compiled using "trap" as the + // failure behavior instead of the default "kill" behavior) when a policy + // path is supplied in the command line arugments. Otherwise the built-in + // pre-compiled policies will be used. + // Refer to the code comment for the "seccomp-log-failures" + // command-line parameter for an explanation about why the |log_failures| + // flag forces the use of .policy files (and the build-time alternative to + // this run-time flag). + let bpf_policy_file = seccomp_policy_path.with_extension("bpf"); + if bpf_policy_file.exists() && !config.log_failures { + j.parse_seccomp_program(&bpf_policy_file).with_context(|| { + format!( + "failed to parse precompiled seccomp policy: {}", + bpf_policy_file.display() + ) + })?; + } else { + // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP, + // which will correctly kill the entire device process if a worker + // thread commits a seccomp violation. + j.set_seccomp_filter_tsync(); + if config.log_failures { + j.log_seccomp_filter_failures(); + } + let bpf_policy_file = seccomp_policy_path.with_extension("policy"); + j.parse_seccomp_filters(&bpf_policy_file).with_context(|| { + format!( + "failed to parse seccomp policy: {}", + bpf_policy_file.display() + ) + })?; } - j.parse_seccomp_filters(&config.seccomp_policy_path.unwrap().with_extension("policy")) - .context("failed to parse seccomp policy")?; + } else { + let bpf_program = EMBEDDED_BPFS + .get(&config.seccomp_policy_name) + .with_context(|| { + format!( + "failed to find embedded seccomp policy: {}", + &config.seccomp_policy_name + ) + })?; + j.parse_seccomp_bytes(bpf_program).with_context(|| { + format!( + "failed to parse embedded seccomp policy: {}", + &config.seccomp_policy_name + ) + })?; } j.use_seccomp_filter(); // Don't do init setup.