Compose selected State Anchor paths over prepared roots
This commit is contained in:
parent
71b0266cf2
commit
4dd2b150d2
1 changed files with 1575 additions and 163 deletions
|
|
@ -32,6 +32,18 @@ pub struct IdMap {
|
|||
pub gid: Vec<IdMapRange>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub enum PreparedDestinationKind {
|
||||
Directory,
|
||||
RegularFile,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct PreparedDestination {
|
||||
pub path: PathBuf,
|
||||
pub kind: PreparedDestinationKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PreparationLimits {
|
||||
pub max_layers: usize,
|
||||
|
|
@ -189,11 +201,91 @@ pub(crate) fn apply_layout(
|
|||
bail!("layer {index} diff-ID mismatch");
|
||||
}
|
||||
validate_tar(&spool, id_map, limits, &mut usage)?;
|
||||
state.apply(&spool, rootfs)?;
|
||||
state.apply(&spool, rootfs, id_map)?;
|
||||
}
|
||||
state.finish(rootfs)
|
||||
}
|
||||
|
||||
pub(crate) fn materialize_destinations(
|
||||
root: &File,
|
||||
destinations: &[PreparedDestination],
|
||||
id_map: &IdMap,
|
||||
) -> Result<()> {
|
||||
if destinations.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let uid = map_owner(0, &id_map.uid, "uid")?;
|
||||
let gid = map_owner(0, &id_map.gid, "gid")?;
|
||||
for destination in destinations {
|
||||
let relative = destination
|
||||
.path
|
||||
.strip_prefix("/")
|
||||
.context("prepared destination is not absolute")?;
|
||||
let relative = normalize_relative(relative)?;
|
||||
let (parent, leaf) = materialize_parent(root, &relative, uid, gid)?;
|
||||
let expected = match destination.kind {
|
||||
PreparedDestinationKind::Directory => FileType::Directory,
|
||||
PreparedDestinationKind::RegularFile => FileType::RegularFile,
|
||||
};
|
||||
match path_type(&parent, Path::new(&leaf))? {
|
||||
Some(actual) if actual == expected => {}
|
||||
Some(_) => bail!("prepared destination conflicts with the image layout"),
|
||||
None => match destination.kind {
|
||||
PreparedDestinationKind::Directory => {
|
||||
mkdirat(&parent, &leaf, Mode::from_raw_mode(0o755))?;
|
||||
set_materialized_metadata(open_component(&parent, &leaf)?, uid, gid)?;
|
||||
}
|
||||
PreparedDestinationKind::RegularFile => {
|
||||
let file = openat2(
|
||||
&parent,
|
||||
&leaf,
|
||||
OFlags::WRONLY
|
||||
| OFlags::CREATE
|
||||
| OFlags::EXCL
|
||||
| OFlags::CLOEXEC
|
||||
| OFlags::NOFOLLOW,
|
||||
Mode::from_raw_mode(0o755),
|
||||
secure_resolve(),
|
||||
)?;
|
||||
set_materialized_metadata(file, uid, gid)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn materialize_parent(root: &File, path: &Path, uid: u32, gid: u32) -> Result<(OwnedFd, OsString)> {
|
||||
let leaf = path
|
||||
.file_name()
|
||||
.context("prepared destination has no final component")?
|
||||
.to_os_string();
|
||||
let mut current = dup(root)?;
|
||||
for component in path.parent().into_iter().flat_map(Path::components) {
|
||||
let Component::Normal(name) = component else {
|
||||
bail!("prepared destination is not normalized");
|
||||
};
|
||||
match mkdirat(¤t, name, Mode::from_raw_mode(0o755)) {
|
||||
Ok(()) => set_materialized_metadata(open_component(¤t, name)?, uid, gid)?,
|
||||
Err(Errno::EXIST) => {}
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
current = open_component(¤t, name)?;
|
||||
}
|
||||
Ok((current, leaf))
|
||||
}
|
||||
|
||||
fn set_materialized_metadata(descriptor: impl AsFd, uid: u32, gid: u32) -> Result<()> {
|
||||
fchown(
|
||||
&descriptor,
|
||||
Some(Uid::from_raw(uid)),
|
||||
Some(Gid::from_raw(gid)),
|
||||
)?;
|
||||
fchmod(&descriptor, Mode::from_raw_mode(0o755))?;
|
||||
futimens(&descriptor, ×tamps(0))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_json<T: serde::de::DeserializeOwned>(
|
||||
layout: &LayoutSource<'_>,
|
||||
path: &Path,
|
||||
|
|
@ -315,7 +407,8 @@ fn validate_tar(
|
|||
let mut paths = HashSet::new();
|
||||
for entry in archive.entries()? {
|
||||
let mut entry = entry?;
|
||||
let path = normalize_relative(&entry.path()?)?;
|
||||
let entry_type = entry.header().entry_type();
|
||||
let path = normalize_layer_path(&entry.path()?, entry_type)?;
|
||||
if !paths.insert(path.clone()) {
|
||||
bail!("policy {POLICY_NAME} rejects duplicate layer path");
|
||||
}
|
||||
|
|
@ -353,9 +446,8 @@ fn validate_tar(
|
|||
bail!("policy {POLICY_NAME} rejects PAX metadata");
|
||||
}
|
||||
}
|
||||
validate_owner(entry.header().uid()?, &id_map.uid, "uid")?;
|
||||
validate_owner(entry.header().gid()?, &id_map.gid, "gid")?;
|
||||
let entry_type = entry.header().entry_type();
|
||||
map_owner(entry.header().uid()?, &id_map.uid, "uid")?;
|
||||
map_owner(entry.header().gid()?, &id_map.gid, "gid")?;
|
||||
if !matches!(
|
||||
entry_type,
|
||||
EntryType::Regular
|
||||
|
|
@ -407,17 +499,35 @@ fn normalize_relative(path: &Path) -> Result<PathBuf> {
|
|||
Ok(normalized)
|
||||
}
|
||||
|
||||
fn validate_owner(value: u64, ranges: &[IdMapRange], name: &str) -> Result<()> {
|
||||
fn normalize_layer_path(path: &Path, entry_type: EntryType) -> Result<PathBuf> {
|
||||
match normalize_relative(path) {
|
||||
Ok(path) => Ok(path),
|
||||
Err(_)
|
||||
if entry_type == EntryType::Directory
|
||||
&& path.components().next().is_some()
|
||||
&& path
|
||||
.components()
|
||||
.all(|component| component == Component::CurDir) =>
|
||||
{
|
||||
Ok(PathBuf::from("."))
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_owner(value: u64, ranges: &[IdMapRange], name: &str) -> Result<u32> {
|
||||
let Ok(value) = u32::try_from(value) else {
|
||||
bail!("{name} {value} is outside the target ID map");
|
||||
};
|
||||
if !ranges
|
||||
.iter()
|
||||
.any(|range| value >= range.container_id && value - range.container_id < range.size)
|
||||
{
|
||||
bail!("{name} {value} is outside the target ID map");
|
||||
for range in ranges {
|
||||
if value >= range.container_id && value - range.container_id < range.size {
|
||||
return range
|
||||
.namespace_id
|
||||
.checked_add(value - range.container_id)
|
||||
.context("mapped owner overflows");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
bail!("{name} {value} is outside the target ID map")
|
||||
}
|
||||
|
||||
fn validate_id_map(id_map: &IdMap) -> Result<()> {
|
||||
|
|
@ -483,7 +593,7 @@ struct LayerState {
|
|||
}
|
||||
|
||||
impl LayerState {
|
||||
fn apply(&mut self, tar_source: &File, root: &File) -> Result<()> {
|
||||
fn apply(&mut self, tar_source: &File, root: &File, id_map: &IdMap) -> Result<()> {
|
||||
apply_whiteouts(tar_source, root, &mut self.directories)?;
|
||||
let mut archive = tar::Archive::new(rewound(tar_source)?);
|
||||
let mut hardlinks = Vec::new();
|
||||
|
|
@ -497,7 +607,7 @@ impl LayerState {
|
|||
if name == ".wh..wh..opq" || name.starts_with(".wh.") {
|
||||
continue;
|
||||
}
|
||||
let metadata = EntryMetadata::from_header(path.clone(), entry.header())?;
|
||||
let metadata = EntryMetadata::from_header(path.clone(), entry.header(), id_map)?;
|
||||
match entry.header().entry_type() {
|
||||
EntryType::Regular | EntryType::Continuous => {
|
||||
forget_directory_tree(&mut self.directories, &path);
|
||||
|
|
@ -557,11 +667,11 @@ struct EntryMetadata {
|
|||
}
|
||||
|
||||
impl EntryMetadata {
|
||||
fn from_header(path: PathBuf, header: &Header) -> Result<Self> {
|
||||
fn from_header(path: PathBuf, header: &Header, id_map: &IdMap) -> Result<Self> {
|
||||
Ok(Self {
|
||||
path,
|
||||
uid: u32::try_from(header.uid()?).context("entry uid exceeds u32")?,
|
||||
gid: u32::try_from(header.gid()?).context("entry gid exceeds u32")?,
|
||||
uid: map_owner(header.uid()?, &id_map.uid, "uid")?,
|
||||
gid: map_owner(header.gid()?, &id_map.gid, "gid")?,
|
||||
mode: header.mode()?,
|
||||
mtime: header.mtime()?,
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue