Refuse inexact production selector authorities

This commit is contained in:
ms2k 2026-07-16 21:06:02 +09:00
commit 4cf7a5e595
2 changed files with 230 additions and 34 deletions

View file

@ -87,6 +87,7 @@ impl PreparedLowerStore {
Err(error) => return Err(error.into()),
}
let root = open_directory_at(&storage_root, Path::new("prepared-lower"))?;
require_owned_directory(&root)?;
Self::initialize_retained(root, catalog)
}
@ -101,7 +102,8 @@ impl PreparedLowerStore {
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
Err(error) => return Err(error.into()),
}
open_directory_at(&store.root, Path::new(name))?;
let directory = open_directory_at(&store.root, Path::new(name))?;
require_owned_directory(&directory)?;
}
open_lock_at(&store.root)?;
Ok(store)
@ -472,6 +474,14 @@ impl PreparedLowerStore {
}
}
fn require_owned_directory(directory: &File) -> Result<()> {
let stat = rustix::fs::fstat(directory)?;
if stat.st_uid != rustix::process::getuid().as_raw() || stat.st_mode & 0o7777 != 0o700 {
bail!("fixed prepared-lower directory must be owned by the invoking user with mode 0700");
}
Ok(())
}
struct Identities {
artifact_id: PreparedArtifactId,
source: SourceIdentity,
@ -685,3 +695,20 @@ fn hex(bytes: &[u8]) -> String {
fn secure_resolve() -> ResolveFlags {
ResolveFlags::BENEATH | ResolveFlags::NO_SYMLINKS | ResolveFlags::NO_MAGICLINKS
}
#[cfg(test)]
mod fixed_directory_tests {
use std::os::unix::fs::PermissionsExt;
use super::*;
#[test]
fn prepared_lower_root_requires_invoking_owner_and_exact_private_mode() {
let directory = tempfile::tempdir().unwrap();
std::fs::set_permissions(directory.path(), std::fs::Permissions::from_mode(0o777)).unwrap();
let root = File::open(directory.path()).unwrap();
assert!(require_owned_directory(&root).is_err());
std::fs::set_permissions(directory.path(), std::fs::Permissions::from_mode(0o700)).unwrap();
assert!(require_owned_directory(&root).is_ok());
}
}

View file

@ -355,15 +355,14 @@ impl LayoutWriter {
staging_id,
} => {
let storage = File::from(storage_root.open_authority()?);
create_directory(&storage, "image-transports")?;
let parent = Arc::new(open_directory(&storage, Path::new("image-transports"))?);
let parent = Arc::new(create_directory(&storage, "image-transports")?);
let name = hex(&staging_id);
mkdirat(&parent, name.as_str(), Mode::from_raw_mode(0o700))?;
let root = Arc::new(open_directory(&parent, Path::new(&name))?);
require_owned_directory(&root)?;
let stat = fstat(&root)?;
create_directory(&root, "blobs")?;
let blobs = open_directory(&root, Path::new("blobs"))?;
create_directory(&blobs, "sha256")?;
let blobs = create_directory(&root, "blobs")?;
drop(create_directory(&blobs, "sha256")?);
write_relative_atomic(
&root,
Path::new("oci-layout"),
@ -522,12 +521,21 @@ impl LayoutWriter {
}
}
fn create_directory(parent: &File, path: &str) -> Result<()> {
fn create_directory(parent: &File, path: &str) -> Result<File> {
match mkdirat(parent, path, Mode::from_raw_mode(0o700)) {
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
Err(error) => return Err(error.into()),
}
drop(open_directory(parent, Path::new(path))?);
let directory = open_directory(parent, Path::new(path))?;
require_owned_directory(&directory)?;
Ok(directory)
}
fn require_owned_directory(directory: &File) -> Result<()> {
let stat = fstat(directory)?;
if stat.st_uid != rustix::process::getuid().as_raw() || stat.st_mode & 0o7777 != 0o700 {
bail!("fixed image-transport directory must be owned by the invoking user with mode 0700");
}
Ok(())
}
@ -695,11 +703,30 @@ fn hex(bytes: &[u8]) -> String {
mod tests {
use std::io::{Read, Write};
use std::net::TcpListener;
use std::os::unix::fs::PermissionsExt;
use oci_client::client::ClientProtocol;
use super::*;
#[test]
fn preexisting_transport_parents_require_exact_private_mode() {
for name in ["image-transports", "blobs", "sha256"] {
let tree = tempfile::tempdir().unwrap();
std::fs::create_dir(tree.path().join(name)).unwrap();
std::fs::set_permissions(
tree.path().join(name),
std::fs::Permissions::from_mode(0o777),
)
.unwrap();
let parent = File::open(tree.path()).unwrap();
assert!(
create_directory(&parent, name).is_err(),
"accepted wrong mode for {name}"
);
}
}
#[test]
fn refuses_mismatched_and_incomplete_platforms() {
assert!(