|
1 | 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2 | 2 | // SPDX-License-Identifier: Apache-2.0
|
3 | 3 |
|
4 |
| -// this build script is called on en every `devtool build`, |
5 |
| -// embedding the FIRECRACKER_VERSION directly in the resulting binary |
| 4 | +use std::path::{Path, PathBuf}; |
| 5 | +use std::process::Command; |
| 6 | +use std::{env, fs}; |
| 7 | + |
| 8 | +const ADVANCED_BINARY_FILTER_FILE_NAME: &str = "seccomp_filter.bpf"; |
| 9 | + |
| 10 | +const JSON_DIR: &str = "../../resources/seccomp"; |
| 11 | +const SECCOMPILER_BUILD_DIR: &str = "../../build/seccompiler"; |
| 12 | +const SECCOMPILER_SRC_DIR: &str = "../seccompiler/src"; |
| 13 | + |
| 14 | +// This script is run on every modification in the target-specific JSON file in `resources/seccomp`. |
| 15 | +// It compiles the JSON seccomp policies into a serializable BPF format, using seccompiler-bin. |
| 16 | +// The generated binary code will get included in Firecracker's code, at compile-time. |
6 | 17 | fn main() {
|
| 18 | + // this build script is called on every `devtool build`, |
| 19 | + // embedding the FIRECRACKER_VERSION directly in the resulting binary |
7 | 20 | let firecracker_version = env!("CARGO_PKG_VERSION").to_string();
|
8 | 21 | println!(
|
9 | 22 | "cargo:rustc-env=FIRECRACKER_VERSION={}",
|
10 | 23 | firecracker_version
|
11 | 24 | );
|
| 25 | + |
| 26 | + // Only compile the seccomp filters for the firecracker binary - otherwise |
| 27 | + // we'll get stuck in an infinite loop as seccompiler-bin (which is invoked below) |
| 28 | + // also executes this build script to set the firecracker version env variable. |
| 29 | + if let Ok(package) = std::env::var("CARGO_MANIFEST_DIR") { |
| 30 | + if !package.ends_with("firecracker") { |
| 31 | + return; |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + cpuid(); |
| 36 | + |
| 37 | + let target = env::var("TARGET").expect("Missing target."); |
| 38 | + let out_dir = env::var("OUT_DIR").expect("Missing build-level OUT_DIR."); |
| 39 | + |
| 40 | + // Path to the JSON seccomp policy. |
| 41 | + let mut json_path = PathBuf::from(JSON_DIR); |
| 42 | + json_path.push(format!("{}.json", target)); |
| 43 | + |
| 44 | + // If the current target doesn't have a default filter, use a default, empty filter. |
| 45 | + // This is to make sure that Firecracker builds even with libc toolchains for which we don't |
| 46 | + // provide a default filter. For example, GNU libc. |
| 47 | + if !json_path.exists() { |
| 48 | + json_path.pop(); |
| 49 | + json_path.push("unimplemented.json"); |
| 50 | + |
| 51 | + println!( |
| 52 | + "cargo:warning=No default seccomp policy for target: {}. Defaulting to \ |
| 53 | + `resources/seccomp/unimplemented.json`.", |
| 54 | + target |
| 55 | + ); |
| 56 | + } |
| 57 | + |
| 58 | + // Retrigger the build script if the JSON file has changed. |
| 59 | + let json_path = json_path.to_str().expect("Invalid bytes"); |
| 60 | + println!("cargo:rerun-if-changed={}", json_path); |
| 61 | + |
| 62 | + // Also retrigger the build script on any seccompiler source code change. |
| 63 | + register_seccompiler_src_watchlist(Path::new(SECCOMPILER_SRC_DIR)); |
| 64 | + |
| 65 | + // Run seccompiler-bin, getting the default, advanced filter. |
| 66 | + let mut bpf_out_path = PathBuf::from(&out_dir); |
| 67 | + bpf_out_path.push(ADVANCED_BINARY_FILTER_FILE_NAME); |
| 68 | + run_seccompiler_bin(json_path, bpf_out_path.to_str().expect("Invalid bytes.")); |
| 69 | +} |
| 70 | + |
| 71 | +// Run seccompiler with the given arguments. |
| 72 | +fn run_seccompiler_bin(json_path: &str, out_path: &str) { |
| 73 | + // We have a global `target` directive in our .cargo/config file specifying x86_64 architecture. |
| 74 | + // However, seccompiler-bin has to be compiled for the host architecture. Without this, cargo |
| 75 | + // would produce a x86_64 binary on aarch64 host, causing this compilation step to fail as such |
| 76 | + // a binary would not be executable. |
| 77 | + let host_arch = env::var("HOST").expect("Could not determine compilation host"); |
| 78 | + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Missing target arch."); |
| 79 | + |
| 80 | + // Command for running seccompiler-bin |
| 81 | + let mut command = Command::new("cargo"); |
| 82 | + command.args([ |
| 83 | + "run", |
| 84 | + "-p", |
| 85 | + "seccompiler", |
| 86 | + "--verbose", |
| 87 | + "--target", |
| 88 | + &host_arch, |
| 89 | + // We need to specify a separate build directory for seccompiler-bin. Otherwise, cargo will |
| 90 | + // deadlock waiting to acquire a lock on the build folder that the parent cargo process is |
| 91 | + // holding. |
| 92 | + "--target-dir", |
| 93 | + SECCOMPILER_BUILD_DIR, |
| 94 | + "--", |
| 95 | + "--input-file", |
| 96 | + json_path, |
| 97 | + "--target-arch", |
| 98 | + &target_arch, |
| 99 | + "--output-file", |
| 100 | + out_path, |
| 101 | + ]); |
| 102 | + |
| 103 | + match command.output() { |
| 104 | + Err(error) => panic!("\nSeccompiler-bin error: {:?}\n", error), |
| 105 | + Ok(result) if !result.status.success() => { |
| 106 | + panic!( |
| 107 | + "\nSeccompiler-bin returned non-zero exit code:\nstderr: {}\nstdout: {}\n", |
| 108 | + String::from_utf8(result.stderr).unwrap(), |
| 109 | + String::from_utf8(result.stdout).unwrap(), |
| 110 | + ); |
| 111 | + } |
| 112 | + Ok(_) => {} |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +// Recursively traverse the entire seccompiler source folder and trigger a re-run of this build |
| 117 | +// script on any modification of these files. |
| 118 | +fn register_seccompiler_src_watchlist(src_dir: &Path) { |
| 119 | + let contents = fs::read_dir(src_dir).expect("Unable to read folder contents."); |
| 120 | + for entry in contents { |
| 121 | + let path = entry.unwrap().path(); |
| 122 | + let metadata = fs::metadata(&path).expect("Unable to read file/folder metadata."); |
| 123 | + |
| 124 | + if metadata.is_file() { |
| 125 | + // Watch all source files. |
| 126 | + println!( |
| 127 | + "cargo:rerun-if-changed={}", |
| 128 | + path.to_str().expect("Invalid unicode bytes.") |
| 129 | + ); |
| 130 | + } else if metadata.is_dir() { |
| 131 | + // If is a folder, recurse. |
| 132 | + register_seccompiler_src_watchlist(&path); |
| 133 | + } |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +fn cpuid() { |
| 138 | + // Sets a `--cfg` flag for conditional compilation. |
| 139 | + // |
| 140 | + // TODO: Use `core::arch::x86_64::has_cpuid` |
| 141 | + // (https://github.com/firecracker-microvm/firecracker/issues/3271). |
| 142 | + #[cfg(any( |
| 143 | + all(target_arch = "x86", target_feature = "sse", not(target_env = "sgx")), |
| 144 | + all(target_arch = "x86_64", not(target_env = "sgx")) |
| 145 | + ))] |
| 146 | + println!("cargo:rustc-cfg=cpuid"); |
12 | 147 | }
|
0 commit comments