logging: Replace nix::Error::EINVAL with more descriptive msgs

Replaces instances of anyhow!(nix::Error::EINVAL) with other messages to
make it easier to debug.

Fixes #954

Signed-off-by: Derek Lee <derlee@redhat.com>
This commit is contained in:
Derek Lee 2022-08-03 16:28:12 -07:00
parent 873e75b915
commit 763ceeb7ba
6 changed files with 121 additions and 52 deletions

View File

@ -174,7 +174,7 @@ impl CgroupManager for Manager {
freezer_controller.freeze()?; freezer_controller.freeze()?;
} }
_ => { _ => {
return Err(anyhow!(nix::Error::EINVAL)); return Err(anyhow!("Invalid FreezerState"));
} }
} }

View File

@ -106,6 +106,11 @@ impl Default for ContainerStatus {
} }
} }
// We might want to change this to thiserror in the future
const MissingCGroupManager: &str = "failed to get container's cgroup Manager";
const MissingLinux: &str = "no linux config";
const InvalidNamespace: &str = "invalid namespace type";
pub type Config = CreateOpts; pub type Config = CreateOpts;
type NamespaceType = String; type NamespaceType = String;
@ -292,7 +297,7 @@ impl Container for LinuxContainer {
self.status.transition(ContainerState::Paused); self.status.transition(ContainerState::Paused);
return Ok(()); return Ok(());
} }
Err(anyhow!("failed to get container's cgroup manager")) Err(anyhow!(MissingCGroupManager))
} }
fn resume(&mut self) -> Result<()> { fn resume(&mut self) -> Result<()> {
@ -310,7 +315,7 @@ impl Container for LinuxContainer {
self.status.transition(ContainerState::Running); self.status.transition(ContainerState::Running);
return Ok(()); return Ok(());
} }
Err(anyhow!("failed to get container's cgroup manager")) Err(anyhow!(MissingCGroupManager))
} }
} }
@ -397,7 +402,7 @@ fn do_init_child(cwfd: RawFd) -> Result<()> {
}; };
if spec.linux.is_none() { if spec.linux.is_none() {
return Err(anyhow!("no linux config")); return Err(anyhow!(MissingLinux));
} }
let linux = spec.linux.as_ref().unwrap(); let linux = spec.linux.as_ref().unwrap();
@ -411,7 +416,7 @@ fn do_init_child(cwfd: RawFd) -> Result<()> {
for ns in &nses { for ns in &nses {
let s = NAMESPACES.get(&ns.r#type.as_str()); let s = NAMESPACES.get(&ns.r#type.as_str());
if s.is_none() { if s.is_none() {
return Err(anyhow!("invalid ns type")); return Err(anyhow!(InvalidNamespace));
} }
let s = s.unwrap(); let s = s.unwrap();
@ -1437,18 +1442,10 @@ impl LinuxContainer {
Some(unistd::getuid()), Some(unistd::getuid()),
Some(unistd::getgid()), Some(unistd::getgid()),
) )
.context(format!("cannot change onwer of container {} root", id))?; .context(format!("Cannot change owner of container {} root", id))?;
if config.spec.is_none() {
return Err(anyhow!(nix::Error::EINVAL));
}
let spec = config.spec.as_ref().unwrap(); let spec = config.spec.as_ref().unwrap();
if spec.linux.is_none() {
return Err(anyhow!(nix::Error::EINVAL));
}
let linux = spec.linux.as_ref().unwrap(); let linux = spec.linux.as_ref().unwrap();
let cpath = if linux.cgroups_path.is_empty() { let cpath = if linux.cgroups_path.is_empty() {
@ -1525,7 +1522,7 @@ pub async fn execute_hook(logger: &Logger, h: &Hook, st: &OCIState) -> Result<()
let binary = PathBuf::from(h.path.as_str()); let binary = PathBuf::from(h.path.as_str());
let path = binary.canonicalize()?; let path = binary.canonicalize()?;
if !path.exists() { if !path.exists() {
return Err(anyhow!(nix::Error::EINVAL)); return Err(anyhow!("Path {:?} does not exist", path));
} }
let mut args = h.args.clone(); let mut args = h.args.clone();

View File

@ -1020,9 +1020,7 @@ pub fn finish_rootfs(cfd_log: RawFd, spec: &Spec, process: &Process) -> Result<(
} }
fn mask_path(path: &str) -> Result<()> { fn mask_path(path: &str) -> Result<()> {
if !path.starts_with('/') || path.contains("..") { check_paths(path)?;
return Err(anyhow!(nix::Error::EINVAL));
}
match mount( match mount(
Some("/dev/null"), Some("/dev/null"),
@ -1040,9 +1038,7 @@ fn mask_path(path: &str) -> Result<()> {
} }
fn readonly_path(path: &str) -> Result<()> { fn readonly_path(path: &str) -> Result<()> {
if !path.starts_with('/') || path.contains("..") { check_paths(path)?;
return Err(anyhow!(nix::Error::EINVAL));
}
if let Err(e) = mount( if let Err(e) = mount(
Some(&path[1..]), Some(&path[1..]),
@ -1068,6 +1064,16 @@ fn readonly_path(path: &str) -> Result<()> {
Ok(()) Ok(())
} }
fn check_paths(path: &str) -> Result<()> {
if !path.starts_with('/') || path.contains("..") {
return Err(anyhow!(
"Cannot mount {} (path does not start with '/' or contains '..').",
path
));
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -1420,6 +1426,55 @@ mod tests {
} }
} }
#[test]
fn test_check_paths() {
#[derive(Debug)]
struct TestData<'a> {
name: &'a str,
path: &'a str,
result: Result<()>,
}
let tests = &[
TestData {
name: "valid path",
path: "/foo/bar",
result: Ok(()),
},
TestData {
name: "does not starts with /",
path: "foo/bar",
result: Err(anyhow!(
"Cannot mount foo/bar (path does not start with '/' or contains '..')."
)),
},
TestData {
name: "contains ..",
path: "../foo/bar",
result: Err(anyhow!(
"Cannot mount ../foo/bar (path does not start with '/' or contains '..')."
)),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d.name);
let result = check_paths(d.path);
let msg = format!("{}: result: {:?}", msg, result);
if d.result.is_ok() {
assert!(result.is_ok());
continue;
}
let expected_error = format!("{}", d.result.as_ref().unwrap_err());
let actual_error = format!("{}", result.unwrap_err());
assert!(actual_error == expected_error, "{}", msg);
}
}
#[test] #[test]
fn test_check_proc_mount() { fn test_check_proc_mount() {
let mount = oci::Mount { let mount = oci::Mount {

View File

@ -4,17 +4,15 @@
// //
use crate::container::Config; use crate::container::Config;
use anyhow::{anyhow, Context, Error, Result}; use anyhow::{anyhow, Context, Result};
use oci::{Linux, LinuxIdMapping, LinuxNamespace, Spec}; use oci::{Linux, LinuxIdMapping, LinuxNamespace, Spec};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Component, PathBuf}; use std::path::{Component, PathBuf};
fn einval() -> Error {
anyhow!(nix::Error::EINVAL)
}
fn get_linux(oci: &Spec) -> Result<&Linux> { fn get_linux(oci: &Spec) -> Result<&Linux> {
oci.linux.as_ref().ok_or_else(einval) oci.linux
.as_ref()
.ok_or_else(|| anyhow!("Unable to get Linux section from Spec"))
} }
fn contain_namespace(nses: &[LinuxNamespace], key: &str) -> bool { fn contain_namespace(nses: &[LinuxNamespace], key: &str) -> bool {
@ -31,7 +29,10 @@ fn rootfs(root: &str) -> Result<()> {
let path = PathBuf::from(root); let path = PathBuf::from(root);
// not absolute path or not exists // not absolute path or not exists
if !path.exists() || !path.is_absolute() { if !path.exists() || !path.is_absolute() {
return Err(einval()); return Err(anyhow!(
"Path from {:?} does not exist or is not absolute",
root
));
} }
// symbolic link? ..? // symbolic link? ..?
@ -49,7 +50,7 @@ fn rootfs(root: &str) -> Result<()> {
if let Some(v) = c.as_os_str().to_str() { if let Some(v) = c.as_os_str().to_str() {
stack.push(v.to_string()); stack.push(v.to_string());
} else { } else {
return Err(einval()); return Err(anyhow!("Invalid path component (unable to convert to str)"));
} }
} }
@ -58,10 +59,13 @@ fn rootfs(root: &str) -> Result<()> {
cleaned.push(e); cleaned.push(e);
} }
let canon = path.canonicalize().context("canonicalize")?; let canon = path.canonicalize().context("failed to canonicalize path")?;
if cleaned != canon { if cleaned != canon {
// There is symbolic in path // There is symbolic in path
return Err(einval()); return Err(anyhow!(
"There may be illegal symbols in the path name. Cleaned ({:?}) and canonicalized ({:?}) paths do not match",
cleaned,
canon));
} }
Ok(()) Ok(())
@ -74,7 +78,7 @@ fn hostname(oci: &Spec) -> Result<()> {
let linux = get_linux(oci)?; let linux = get_linux(oci)?;
if !contain_namespace(&linux.namespaces, "uts") { if !contain_namespace(&linux.namespaces, "uts") {
return Err(einval()); return Err(anyhow!("Linux namespace does not contain uts"));
} }
Ok(()) Ok(())
@ -88,7 +92,7 @@ fn security(oci: &Spec) -> Result<()> {
} }
if !contain_namespace(&linux.namespaces, "mount") { if !contain_namespace(&linux.namespaces, "mount") {
return Err(einval()); return Err(anyhow!("Linux namespace does not contain mount"));
} }
// don't care about selinux at present // don't care about selinux at present
@ -103,7 +107,7 @@ fn idmapping(maps: &[LinuxIdMapping]) -> Result<()> {
} }
} }
Err(einval()) Err(anyhow!("No idmap has size > 0"))
} }
fn usernamespace(oci: &Spec) -> Result<()> { fn usernamespace(oci: &Spec) -> Result<()> {
@ -121,7 +125,7 @@ fn usernamespace(oci: &Spec) -> Result<()> {
} else { } else {
// no user namespace but idmap // no user namespace but idmap
if !linux.uid_mappings.is_empty() || !linux.gid_mappings.is_empty() { if !linux.uid_mappings.is_empty() || !linux.gid_mappings.is_empty() {
return Err(einval()); return Err(anyhow!("No user namespace, but uid or gid mapping exists"));
} }
} }
@ -163,7 +167,7 @@ fn sysctl(oci: &Spec) -> Result<()> {
if contain_namespace(&linux.namespaces, "ipc") { if contain_namespace(&linux.namespaces, "ipc") {
continue; continue;
} else { } else {
return Err(einval()); return Err(anyhow!("Linux namespace does not contain ipc"));
} }
} }
@ -178,11 +182,11 @@ fn sysctl(oci: &Spec) -> Result<()> {
} }
if key == "kernel.hostname" { if key == "kernel.hostname" {
return Err(einval()); return Err(anyhow!("Kernel hostname specfied in Spec"));
} }
} }
return Err(einval()); return Err(anyhow!("Sysctl config contains invalid settings"));
} }
Ok(()) Ok(())
} }
@ -191,12 +195,13 @@ fn rootless_euid_mapping(oci: &Spec) -> Result<()> {
let linux = get_linux(oci)?; let linux = get_linux(oci)?;
if !contain_namespace(&linux.namespaces, "user") { if !contain_namespace(&linux.namespaces, "user") {
return Err(einval()); return Err(anyhow!("Linux namespace is missing user"));
} }
if linux.uid_mappings.is_empty() || linux.gid_mappings.is_empty() { if linux.uid_mappings.is_empty() || linux.gid_mappings.is_empty() {
// rootless containers requires at least one UID/GID mapping return Err(anyhow!(
return Err(einval()); "Rootless containers require at least one UID/GID mapping"
));
} }
Ok(()) Ok(())
@ -220,7 +225,7 @@ fn rootless_euid_mount(oci: &Spec) -> Result<()> {
let fields: Vec<&str> = opt.split('=').collect(); let fields: Vec<&str> = opt.split('=').collect();
if fields.len() != 2 { if fields.len() != 2 {
return Err(einval()); return Err(anyhow!("Options has invalid field: {:?}", fields));
} }
let id = fields[1] let id = fields[1]
@ -229,11 +234,11 @@ fn rootless_euid_mount(oci: &Spec) -> Result<()> {
.context(format!("parse field {}", &fields[1]))?; .context(format!("parse field {}", &fields[1]))?;
if opt.starts_with("uid=") && !has_idmapping(&linux.uid_mappings, id) { if opt.starts_with("uid=") && !has_idmapping(&linux.uid_mappings, id) {
return Err(einval()); return Err(anyhow!("uid of {} does not have a valid mapping", id));
} }
if opt.starts_with("gid=") && !has_idmapping(&linux.gid_mappings, id) { if opt.starts_with("gid=") && !has_idmapping(&linux.gid_mappings, id) {
return Err(einval()); return Err(anyhow!("gid of {} does not have a valid mapping", id));
} }
} }
} }
@ -249,15 +254,18 @@ fn rootless_euid(oci: &Spec) -> Result<()> {
pub fn validate(conf: &Config) -> Result<()> { pub fn validate(conf: &Config) -> Result<()> {
lazy_static::initialize(&SYSCTLS); lazy_static::initialize(&SYSCTLS);
let oci = conf.spec.as_ref().ok_or_else(einval)?; let oci = conf
.spec
.as_ref()
.ok_or_else(|| anyhow!("Invalid config spec"))?;
if oci.linux.is_none() { if oci.linux.is_none() {
return Err(einval()); return Err(anyhow!("oci Linux is none"));
} }
let root = match oci.root.as_ref() { let root = match oci.root.as_ref() {
Some(v) => v.path.as_str(), Some(v) => v.path.as_str(),
None => return Err(einval()), None => return Err(anyhow!("oci root is none")),
}; };
rootfs(root).context("rootfs")?; rootfs(root).context("rootfs")?;

View File

@ -523,7 +523,7 @@ impl Handle {
.as_ref() .as_ref()
.map(|to| to.address.as_str()) // Extract address field .map(|to| to.address.as_str()) // Extract address field
.and_then(|addr| if addr.is_empty() { None } else { Some(addr) }) // Make sure it's not empty .and_then(|addr| if addr.is_empty() { None } else { Some(addr) }) // Make sure it's not empty
.ok_or_else(|| anyhow!(nix::Error::EINVAL))?; .ok_or_else(|| anyhow!("Unable to determine ip address of ARP neighbor"))?;
let ip = IpAddr::from_str(ip_address) let ip = IpAddr::from_str(ip_address)
.map_err(|e| anyhow!("Failed to parse IP {}: {:?}", ip_address, e))?; .map_err(|e| anyhow!("Failed to parse IP {}: {:?}", ip_address, e))?;
@ -612,7 +612,12 @@ fn parse_mac_address(addr: &str) -> Result<[u8; 6]> {
// Parse single Mac address block // Parse single Mac address block
let mut parse_next = || -> Result<u8> { let mut parse_next = || -> Result<u8> {
let v = u8::from_str_radix(split.next().ok_or_else(|| anyhow!(nix::Error::EINVAL))?, 16)?; let v = u8::from_str_radix(
split
.next()
.ok_or_else(|| anyhow!("Invalid MAC address {}", addr))?,
16,
)?;
Ok(v) Ok(v)
}; };

View File

@ -380,7 +380,7 @@ impl AgentService {
let mut process = req let mut process = req
.process .process
.into_option() .into_option()
.ok_or_else(|| anyhow!(nix::Error::EINVAL))?; .ok_or_else(|| anyhow!("Unable to parse process from ExecProcessRequest"))?;
// Apply any necessary corrections for PCI addresses // Apply any necessary corrections for PCI addresses
update_env_pci(&mut process.Env, &sandbox.pcimap)?; update_env_pci(&mut process.Env, &sandbox.pcimap)?;
@ -629,7 +629,7 @@ impl AgentService {
}; };
if reader.is_none() { if reader.is_none() {
return Err(anyhow!(nix::Error::EINVAL)); return Err(anyhow!("Unable to determine stream reader, is None"));
} }
let reader = reader.ok_or_else(|| anyhow!("cannot get stream reader"))?; let reader = reader.ok_or_else(|| anyhow!("cannot get stream reader"))?;
@ -1843,7 +1843,11 @@ fn do_copy_file(req: &CopyFileRequest) -> Result<()> {
let path = PathBuf::from(req.path.as_str()); let path = PathBuf::from(req.path.as_str());
if !path.starts_with(CONTAINER_BASE) { if !path.starts_with(CONTAINER_BASE) {
return Err(anyhow!(nix::Error::EINVAL)); return Err(anyhow!(
"Path {:?} does not start with {}",
path,
CONTAINER_BASE
));
} }
let parent = path.parent(); let parent = path.parent();