Revert "crosvm: Embed seccomp filters into binary"

This reverts commit a78f92cdb2.

Reason for revert: doesn't build on Chrome OS

Original change's description:
> crosvm: Embed seccomp filters into binary
>
> Seccomp policy files will now pre-compile to bpf bytecode for
> target architecture and embedded in the crosvm binary.
> When minijail is not checked out in crosvm tree as a submodule,
> MINIJAIL_DIR environment variable needs to be specified for the
> policy compiler to run.
>
> TEST=all tests passed, vm runs fine with sandbox on and no separate
> policy files present.
> BUG=b:235858187
>
> Change-Id: Ia801966df0a8adfdc4a80f5899e33121fe45e5f9
> Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3774318
> Reviewed-by: Dennis Kempin <denniskempin@google.com>
> Commit-Queue: Zihan Chen <zihanchen@google.com>
> Tested-by: Zihan Chen <zihanchen@google.com>

Bug: b:235858187
Change-Id: Ia81e43185d5f16bd061b6d0290befb4642c44548
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3813056
Tested-by: Junichi Uekawa <uekawa@chromium.org>
Commit-Queue: Junichi Uekawa <uekawa@chromium.org>
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
This commit is contained in:
Junichi Uekawa 2022-08-05 06:56:52 +00:00 committed by crosvm LUCI
parent 2bb5d198fd
commit eefbf6da74
5 changed files with 42 additions and 226 deletions

View file

@ -229,9 +229,6 @@ tube_transporter = { path = "tube_transporter" }
winapi = "*"
win_util = { path = "win_util"}
[build-dependencies]
cc = "*"
[dev-dependencies]
base = "*"

158
build.rs
View file

@ -1,158 +0,0 @@
// 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.
#[cfg(unix)]
use std::env;
use std::fs;
use std::path::Path;
#[cfg(unix)]
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();
}
#[cfg(unix)]
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=seccomp");
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);
}
#[cfg(not(unix))]
fn main() {
println!("cargo:rerun-if-changed=build.rs");
}

View file

@ -172,13 +172,19 @@ 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 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.
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).
- 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

View file

@ -66,6 +66,7 @@ cfg_if::cfg_if! {
static KVM_PATH: &str = "/dev/kvm";
static VHOST_NET_PATH: &str = "/dev/vhost-net";
static SECCOMP_POLICY_DIR: &str = "/usr/share/policy/crosvm";
} else if #[cfg(windows)] {
use base::{Event, Tube};
@ -525,12 +526,18 @@ 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<PathBuf> {
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<PathBuf>,
#[serde(default)]
pub seccomp_log_failures: bool,
@ -541,7 +548,7 @@ impl Default for JailConfig {
JailConfig {
pivot_root: jail_config_default_pivot_root(),
#[cfg(unix)]
seccomp_policy_dir: None,
seccomp_policy_dir: jail_config_default_seccomp_policy_dir(),
seccomp_log_failures: false,
}
}
@ -2189,7 +2196,7 @@ mod tests {
JailConfig {
pivot_root: jail_config_default_pivot_root(),
#[cfg(unix)]
seccomp_policy_dir: None,
seccomp_policy_dir: jail_config_default_seccomp_policy_dir(),
seccomp_log_failures: false,
}
);

View file

@ -13,18 +13,10 @@ 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;
static EMBEDDED_BPFS: Lazy<std::collections::HashMap<&str, Vec<u8>>> = Lazy::new(|| {
if cfg!(unix) {
include!(concat!(env!("OUT_DIR"), "/bpf_includes.in"))
} else {
std::collections::HashMap::<&str, Vec<u8>>::new()
}
});
#[allow(dead_code)]
pub(super) struct SandboxConfig<'a> {
pub(super) limit_caps: bool,
pub(super) log_failures: bool,
@ -75,55 +67,27 @@ pub(super) fn create_base_minijail(
// Don't allow the device to gain new privileges.
j.no_new_privs();
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()
)
})?;
}
// 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 {
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
)
})?;
// 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();
}
j.parse_seccomp_filters(&config.seccomp_policy_path.unwrap().with_extension("policy"))
.context("failed to parse seccomp policy")?;
}
j.use_seccomp_filter();
// Don't do init setup.