use anyhow::Context;
use libc::c_ulong;
use nix::unistd::{Gid, Pid, Uid};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use crate::ApparmorMode;
use crate::lsm::LSMProfile;
use crate::result::Result;
pub(crate) fn get_path() -> PathBuf {
PathBuf::from(&env::var_os("CNTR_PROC").unwrap_or_else(|| OsString::from("/proc")))
}
fn translate_id(map_path: &Path, outer_id: u32) -> Result<u32> {
let contents = std::fs::read_to_string(map_path)
.with_context(|| format!("failed to read {:?}", map_path))?;
for line in contents.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 3 {
continue;
}
let inner_start: u32 = parts[0]
.parse()
.with_context(|| format!("failed to parse inner ID in {:?}", map_path))?;
let outer_start: u32 = parts[1]
.parse()
.with_context(|| format!("failed to parse outer ID in {:?}", map_path))?;
let length: u32 = parts[2]
.parse()
.with_context(|| format!("failed to parse length in {:?}", map_path))?;
if let Some(offset) = outer_id.checked_sub(outer_start)
&& offset < length
{
let inner = inner_start.checked_add(offset).ok_or_else(|| {
anyhow::anyhow!(
"integer overflow computing inner ID in {:?}: {} + {} would overflow",
map_path,
inner_start,
offset
)
})?;
return Ok(inner);
}
}
Ok(outer_id)
}
pub(crate) struct ProcStatus {
pub(crate) global_pid: Pid,
pub(crate) effective_capabilities: c_ulong,
pub(crate) last_cap: c_ulong,
pub(crate) uid: Uid,
pub(crate) gid: Gid,
pub(crate) lsm_profile: Option<LSMProfile>,
}
pub(crate) fn status(target_pid: Pid, apparmor_mode: ApparmorMode) -> Result<ProcStatus> {
let path = get_path().join(target_pid.to_string()).join("status");
let file = File::open(&path)
.with_context(|| format!("failed to open process status file {}", path.display()))?;
let mut effective_caps: Option<c_ulong> = None;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line.with_context(|| format!("could not read line from {}", path.display()))?;
let columns: Vec<&str> = line.split('\t').collect();
if columns.len() < 2 {
anyhow::bail!(
"malformed line in {} (expected at least 2 tab-separated columns, found {}): '{}'",
path.display(),
columns.len(),
line
);
}
if columns[0] == "CapEff:"
&& let Some(cap_string) = columns.last()
{
let cap = c_ulong::from_str_radix(cap_string, 16).with_context(|| {
format!(
"failed to parse capability '{}' from {}",
cap_string,
path.display()
)
})?;
effective_caps = Some(cap);
}
}
let effective_capabilities = effective_caps.ok_or_else(|| {
anyhow::anyhow!(
"could not find effective capabilities (CapEff) in {}",
path.display()
)
})?;
let cap_last_cap_path = get_path().join("sys/kernel/cap_last_cap");
let cap_contents = std::fs::read_to_string(&cap_last_cap_path)
.with_context(|| format!("failed to read {}", cap_last_cap_path.display()))?;
let cap_contents_trimmed = cap_contents.trim();
let last_cap = cap_contents_trimmed.parse::<c_ulong>().with_context(|| {
format!(
"failed to parse last capability value from {}: '{}'",
cap_last_cap_path.display(),
cap_contents_trimmed
)
})?;
use std::fs::metadata;
use std::os::unix::fs::MetadataExt;
let proc_dir = get_path().join(target_pid.to_string());
let metadata = metadata(&proc_dir)
.with_context(|| format!("failed to get metadata for {}", proc_dir.display()))?;
let host_uid = metadata.uid();
let host_gid = metadata.gid();
let container_uid = translate_id(&proc_dir.join("uid_map"), host_uid)
.with_context(|| format!("failed to translate host UID {} to container UID", host_uid))?;
let container_gid = translate_id(&proc_dir.join("gid_map"), host_gid)
.with_context(|| format!("failed to translate host GID {} to container GID", host_gid))?;
let uid = Uid::from_raw(container_uid);
let gid = Gid::from_raw(container_gid);
let lsm_profile =
crate::lsm::read_profile(target_pid, apparmor_mode).context("failed to get lsm profile")?;
Ok(ProcStatus {
global_pid: target_pid,
effective_capabilities,
last_cap,
uid,
gid,
lsm_profile,
})
}