-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide a way of detecting system library leakage (#5259)
After #5158 was integrated @Rain noticed that attempting to run a build of `omdb` in the switch zone suddenly stopped working and filed oxidecomputer/helios-omicron-brand#15. @jgallagher ended up fixing this by splitting out the sled-hardware types into their own crate in #5245. We decided it would be good if we added some sort of CI check to omicron to catch these library leakages earlier. This PR introduces that check and adds it to the helios build and test buildomat job. I have also added some notes to the readme for others that may end up adding a new library dependency. Locally I modified the allow list so that it would produce errors, those errors end up looking like: ``` $ cargo xtask verify-libraries Finished dev [unoptimized + debuginfo] target(s) in 0.42s Running `target/debug/xtask verify-libraries` Finished dev [unoptimized + debuginfo] target(s) in 4.11s Error: Found library issues with the following: installinator UNEXPECTED dependency on libipcc.so.1 omicron-dev UNEXPECTED dependency on libipcc.so.1 UNEXPECTED dependency on libresolv.so.2 sp-sim UNEXPECTED dependency on libipcc.so.1 UNEXPECTED dependency on libresolv.so.2 sled-agent NEEDS libnvme.so.1 but is not allowed mgs UNEXPECTED dependency on libipcc.so.1 UNEXPECTED dependency on libresolv.so.2 If depending on a new library was intended please add it to xtask.toml ```
- Loading branch information
1 parent
946e4db
commit 5d97551
Showing
7 changed files
with
224 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# This config file is used by `cargo xtask verify-libraries` | ||
|
||
|
||
# These are libraries that we expect to show up in any executable produced | ||
# by the omicron repo. | ||
[libraries."libc.so.1"] | ||
[libraries."libcontract.so.1"] | ||
[libraries."libcrypto.so.3"] | ||
[libraries."libdevinfo.so.1"] | ||
[libraries."libdlpi.so.1"] | ||
[libraries."libdoor.so.1"] | ||
[libraries."libefi.so.1"] | ||
[libraries."libgcc_s.so.1"] | ||
[libraries."libipcc.so.1"] | ||
[libraries."libkstat.so.1"] | ||
[libraries."libm.so.2"] | ||
[libraries."libnsl.so.1"] | ||
[libraries."libnvpair.so.1"] | ||
[libraries."libpq.so.5"] | ||
[libraries."libpthread.so.1"] | ||
[libraries."libresolv.so.2"] | ||
[libraries."librt.so.1"] | ||
[libraries."libscf.so.1"] | ||
[libraries."libsocket.so.1"] | ||
[libraries."libssl.so.3"] | ||
[libraries."libumem.so.1"] | ||
[libraries."libxml2.so.2"] | ||
[libraries."libxmlsec1.so.1"] | ||
|
||
# libnvme is a global zone only library and therefore we must be sure that only | ||
# programs running in the gz require it. Additionally only sled-agent should be | ||
# managing a sled's hardware. | ||
[libraries."libnvme.so.1"] | ||
binary_allow_list = [ | ||
"installinator", | ||
"omicron-dev", | ||
"omicron-package", | ||
"services-ledger-check-migrate", | ||
"sled-agent", | ||
"sled-agent-sim", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
use anyhow::{bail, Context, Result}; | ||
use camino::Utf8Path; | ||
use cargo_metadata::Message; | ||
use fs_err as fs; | ||
use serde::Deserialize; | ||
use std::{ | ||
collections::{BTreeMap, BTreeSet}, | ||
io::BufReader, | ||
process::{Command, Stdio}, | ||
}; | ||
use swrite::{swriteln, SWrite}; | ||
|
||
use crate::load_workspace; | ||
|
||
#[derive(Deserialize, Debug)] | ||
struct LibraryConfig { | ||
binary_allow_list: Option<BTreeSet<String>>, | ||
} | ||
|
||
#[derive(Deserialize, Debug)] | ||
struct XtaskConfig { | ||
libraries: BTreeMap<String, LibraryConfig>, | ||
} | ||
|
||
#[derive(Debug)] | ||
enum LibraryError { | ||
Unexpected(String), | ||
NotAllowed(String), | ||
} | ||
|
||
/// Verify that the binary at the provided path complies with the rules laid out | ||
/// in the xtask.toml config file. Errors are pushed to a hashmap so that we can | ||
/// display to a user the entire list of issues in one go. | ||
fn verify_executable( | ||
config: &XtaskConfig, | ||
path: &Utf8Path, | ||
errors: &mut BTreeMap<String, Vec<LibraryError>>, | ||
) -> Result<()> { | ||
let binary = path.file_name().context("basename of executable")?; | ||
|
||
let command = Command::new("elfedit") | ||
.args(["-o", "simple", "-r", "-e", "dyn:tag NEEDED"]) | ||
.arg(path) | ||
.output() | ||
.context("exec elfedit")?; | ||
|
||
if !command.status.success() { | ||
bail!("Failed to execute elfedit successfully {}", command.status); | ||
} | ||
|
||
let stdout = String::from_utf8(command.stdout)?; | ||
// `elfedit -o simple -r -e "dyn:tag NEEDED" /file/path` will return | ||
// a new line seperated list of required libraries so we walk over | ||
// them looking for a match in our configuration file. If we find | ||
// the library we make sure the binary is allowed to pull it in via | ||
// the whitelist. | ||
for library in stdout.lines() { | ||
let library_config = match config.libraries.get(library.trim()) { | ||
Some(config) => config, | ||
None => { | ||
errors | ||
.entry(binary.to_string()) | ||
.or_default() | ||
.push(LibraryError::Unexpected(library.to_string())); | ||
|
||
continue; | ||
} | ||
}; | ||
|
||
if let Some(allowed) = &library_config.binary_allow_list { | ||
if !allowed.contains(binary) { | ||
errors | ||
.entry(binary.to_string()) | ||
.or_default() | ||
.push(LibraryError::NotAllowed(library.to_string())); | ||
} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
pub fn cmd_verify_libraries() -> Result<()> { | ||
let metadata = load_workspace()?; | ||
let mut config_path = metadata.workspace_root; | ||
config_path.push(".cargo/xtask.toml"); | ||
let config = read_xtask_toml(&config_path)?; | ||
|
||
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); | ||
let mut command = Command::new(cargo) | ||
.args(["build", "--bins", "--message-format=json-render-diagnostics"]) | ||
.stdout(Stdio::piped()) | ||
.spawn() | ||
.context("failed to spawn cargo build")?; | ||
|
||
let reader = BufReader::new(command.stdout.take().context("take stdout")?); | ||
|
||
let mut errors = Default::default(); | ||
for message in cargo_metadata::Message::parse_stream(reader) { | ||
if let Message::CompilerArtifact(artifact) = message? { | ||
// We are only interested in artifacts that are binaries | ||
if let Some(executable) = artifact.executable { | ||
verify_executable(&config, &executable, &mut errors)?; | ||
} | ||
} | ||
} | ||
|
||
let status = command.wait()?; | ||
if !status.success() { | ||
bail!("Failed to execute cargo build successfully {}", status); | ||
} | ||
|
||
if !errors.is_empty() { | ||
let mut msg = String::new(); | ||
errors.iter().for_each(|(binary, errors)| { | ||
swriteln!(msg, "{binary}"); | ||
errors.iter().for_each(|error| match error { | ||
LibraryError::Unexpected(lib) => { | ||
swriteln!(msg, "\tUNEXPECTED dependency on {lib}"); | ||
} | ||
LibraryError::NotAllowed(lib) => { | ||
swriteln!(msg, "\tNEEDS {lib} but is not allowed"); | ||
} | ||
}); | ||
}); | ||
|
||
bail!( | ||
"Found library issues with the following:\n{msg}\n\n\ | ||
If depending on a new library was intended please add it to xtask.toml" | ||
); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn read_xtask_toml(path: &Utf8Path) -> Result<XtaskConfig> { | ||
let config_str = fs::read_to_string(path)?; | ||
toml::from_str(&config_str).with_context(|| format!("parse {:?}", path)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters