Confine package ownership changes to private mounts
This commit is contained in:
parent
2c420012bd
commit
8334fd379f
3 changed files with 402 additions and 25 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
|
|
@ -40,17 +41,19 @@ use validation::{
|
|||
};
|
||||
|
||||
pub const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
|
||||
pub const RUNSC_BUILD_ID: &str = "904ab3c9bd4c99a27181076f2c9ea2fecc041db5a728c999540d6664174d2cd4";
|
||||
pub const RUNSC_BUILD_ID: &str = "b9ad6f93a8e890b5935061582b1a128682f3fd41c8053c54d0e44ff45d809971";
|
||||
pub const RUNSC_BUILD_ID_BYTES: [u8; 32] = [
|
||||
144, 74, 179, 201, 189, 76, 153, 162, 113, 129, 7, 111, 44, 158, 162, 254, 204, 4, 29, 181,
|
||||
167, 40, 201, 153, 84, 13, 102, 100, 23, 77, 44, 212,
|
||||
185, 173, 111, 147, 168, 232, 144, 181, 147, 80, 97, 88, 43, 26, 18, 134, 130, 243, 253, 65,
|
||||
200, 5, 60, 84, 208, 228, 79, 244, 93, 128, 153, 113,
|
||||
];
|
||||
pub const RUNSC_EXECUTABLE_NAME: &str =
|
||||
"lazaret-runsc-904ab3c9bd4c99a27181076f2c9ea2fecc041db5a728c999540d6664174d2cd4";
|
||||
"lazaret-runsc-b9ad6f93a8e890b5935061582b1a128682f3fd41c8053c54d0e44ff45d809971";
|
||||
pub const DOMAIN_OPEN_FILE_LIMIT: u64 = 524_288;
|
||||
|
||||
const OCI_VERSION: &str = "1.2.1";
|
||||
const MAX_TERMINFO_BYTES: usize = 1_048_576;
|
||||
const GVISOR_MOUNT_ANNOTATION_PREFIX: &str = "dev.gvisor.spec.mount.";
|
||||
const OWNER_CHANGE_MOUNTS_ANNOTATION: &str = "dev.lazaret.runsc.owner-change-mounts";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum SandboxBuildErrorKind {
|
||||
|
|
@ -691,8 +694,10 @@ fn build_sandbox_bundle_from_source(
|
|||
.readonly(false)
|
||||
.build()
|
||||
.map_err(oci_error)?;
|
||||
let annotations = build_mount_annotations(request.configuration, &mounts)?;
|
||||
let specification = SpecBuilder::default()
|
||||
.version(OCI_VERSION)
|
||||
.annotations(annotations)
|
||||
.root(root)
|
||||
.mounts(mounts)
|
||||
.process(process)
|
||||
|
|
@ -723,6 +728,54 @@ fn build_sandbox_bundle_from_source(
|
|||
})
|
||||
}
|
||||
|
||||
fn build_mount_annotations(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
mounts: &[Mount],
|
||||
) -> Result<HashMap<String, String>, SandboxBuildError> {
|
||||
let owner_change_mounts = std::iter::once("/".to_owned())
|
||||
.chain(
|
||||
configuration
|
||||
.fingerprint_record()
|
||||
.persistent_dirs
|
||||
.iter()
|
||||
.cloned(),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut annotations = HashMap::from([(
|
||||
OWNER_CHANGE_MOUNTS_ANNOTATION.to_owned(),
|
||||
serde_json::to_string(&owner_change_mounts).map_err(|error| {
|
||||
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
||||
})?,
|
||||
)]);
|
||||
for (index, mount) in mounts.iter().enumerate() {
|
||||
let destination = mount.destination().to_str().ok_or_else(|| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::InvalidPath,
|
||||
"mount destination is not UTF-8",
|
||||
)
|
||||
})?;
|
||||
if mount.typ().as_deref() != Some("bind")
|
||||
|| owner_change_mounts
|
||||
.iter()
|
||||
.any(|allowed| allowed == destination)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(source) = mount.source().as_ref().and_then(|path| path.to_str()) else {
|
||||
return Err(SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::InvalidPath,
|
||||
"bind mount source is not UTF-8",
|
||||
));
|
||||
};
|
||||
let prefix = format!("{GVISOR_MOUNT_ANNOTATION_PREFIX}lazaret{index}.");
|
||||
annotations.insert(format!("{prefix}source"), source.to_owned());
|
||||
annotations.insert(format!("{prefix}type"), "bind".to_owned());
|
||||
annotations.insert(format!("{prefix}share"), "shared".to_owned());
|
||||
annotations.insert(format!("{prefix}directfs"), "off".to_owned());
|
||||
}
|
||||
Ok(annotations)
|
||||
}
|
||||
|
||||
fn canonical_specification_json(specification: &Spec) -> Result<Vec<u8>, SandboxBuildError> {
|
||||
let mut value = serde_json::to_value(specification).map_err(|error| {
|
||||
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
||||
|
|
|
|||
|
|
@ -338,8 +338,15 @@ pub(super) fn build_process(
|
|||
root_process: RootProcess,
|
||||
_terminal: Option<&StagedHostTerminal>,
|
||||
) -> Result<oci_spec::runtime::Process, SandboxBuildError> {
|
||||
static IDENTITY_TRANSITION_CAPABILITIES: LazyLock<Capabilities> =
|
||||
LazyLock::new(|| Capabilities::from([Capability::Setgid, Capability::Setuid]));
|
||||
static IDENTITY_TRANSITION_CAPABILITIES: LazyLock<Capabilities> = LazyLock::new(|| {
|
||||
Capabilities::from([
|
||||
Capability::Chown,
|
||||
Capability::DacOverride,
|
||||
Capability::Fowner,
|
||||
Capability::Setgid,
|
||||
Capability::Setuid,
|
||||
])
|
||||
});
|
||||
let empty = Capabilities::new();
|
||||
let identity = IDENTITY_TRANSITION_CAPABILITIES.clone();
|
||||
let capabilities = LinuxCapabilitiesBuilder::default()
|
||||
|
|
|
|||
|
|
@ -303,14 +303,42 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
|||
assert_eq!(
|
||||
specification["process"]["capabilities"],
|
||||
serde_json::json!({
|
||||
"bounding": ["CAP_SETGID", "CAP_SETUID"],
|
||||
"effective": ["CAP_SETGID", "CAP_SETUID"],
|
||||
"bounding": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_SETGID", "CAP_SETUID"],
|
||||
"effective": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_SETGID", "CAP_SETUID"],
|
||||
"inheritable": [],
|
||||
"permitted": ["CAP_SETGID", "CAP_SETUID"],
|
||||
"permitted": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_SETGID", "CAP_SETUID"],
|
||||
"ambient": []
|
||||
})
|
||||
);
|
||||
|
||||
let annotations = specification["annotations"].as_object().unwrap();
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Vec<String>>(
|
||||
annotations["dev.lazaret.runsc.owner-change-mounts"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap(),
|
||||
["/", "/root", "/usr", "/var", "/etc", "/opt"]
|
||||
);
|
||||
let owner_change_mounts = BTreeSet::from(["/", "/root", "/usr", "/var", "/etc", "/opt"]);
|
||||
for (index, mount) in specification["mounts"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let destination = mount["destination"].as_str().unwrap();
|
||||
if mount["type"] != "bind" || owner_change_mounts.contains(destination) {
|
||||
continue;
|
||||
}
|
||||
let prefix = format!("dev.gvisor.spec.mount.lazaret{index}.");
|
||||
assert_eq!(annotations[&format!("{prefix}source")], mount["source"]);
|
||||
assert_eq!(annotations[&format!("{prefix}type")], "bind");
|
||||
assert_eq!(annotations[&format!("{prefix}share")], "shared");
|
||||
assert_eq!(annotations[&format!("{prefix}directfs")], "off");
|
||||
}
|
||||
|
||||
let namespaces = specification["linux"]["namespaces"].as_array().unwrap();
|
||||
assert_eq!(
|
||||
namespaces
|
||||
|
|
|
|||
Loading…
Reference in a new issue