Confine terminfo injection to runtime control state
This commit is contained in:
parent
cf5948e8be
commit
54f3a08b91
1 changed files with 360 additions and 96 deletions
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use lazaret_core::configuration::{
|
||||
|
|
@ -11,16 +12,15 @@ use lazaret_runtime_supervisor::sandbox::{
|
|||
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, HostTerminal,
|
||||
MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, RootProcess,
|
||||
SandboxBuildErrorKind, SandboxBuildRequest, TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION,
|
||||
build_sandbox_bundle, production_sandbox_layout,
|
||||
build_sandbox_bundle, production_sandbox_layout, stage_host_terminal,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use tempfile::TempDir;
|
||||
|
||||
struct Fixture {
|
||||
tree: TempDir,
|
||||
_tree: TempDir,
|
||||
configuration: ResolvedDomainConfiguration,
|
||||
terminfo_source: PathBuf,
|
||||
terminfo_root: PathBuf,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
|
|
@ -74,26 +74,44 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
|||
sandbox_layout: production_sandbox_layout().unwrap(),
|
||||
})
|
||||
.unwrap();
|
||||
let terminfo_root = tree.path().join("trusted-terminfo");
|
||||
let terminfo_source = terminfo_root.join("x").join("xterm-lazaret");
|
||||
let terminfo_source = tree
|
||||
.path()
|
||||
.join("external-host-terminfo")
|
||||
.join("x")
|
||||
.join("xterm-lazaret");
|
||||
fs::create_dir_all(terminfo_source.parent().unwrap()).unwrap();
|
||||
fs::write(&terminfo_source, b"compiled host terminfo capabilities").unwrap();
|
||||
fs::write(&terminfo_source, compiled_terminfo("xterm-lazaret")).unwrap();
|
||||
Self {
|
||||
tree,
|
||||
_tree: tree,
|
||||
configuration,
|
||||
terminfo_source,
|
||||
terminfo_root,
|
||||
}
|
||||
}
|
||||
|
||||
fn terminal(&self) -> HostTerminal {
|
||||
HostTerminal::from_trusted_roots(
|
||||
fn terminal(&self) -> lazaret_runtime_supervisor::sandbox::StagedHostTerminal {
|
||||
let terminal = HostTerminal::from_compiled_entry(
|
||||
"xterm-lazaret",
|
||||
Some("truecolor".to_owned()),
|
||||
&self.terminfo_source,
|
||||
std::slice::from_ref(&self.terminfo_root),
|
||||
fs::read(&self.terminfo_source).unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
stage_host_terminal(&self.configuration, ContainerId::new([0x11; 32]), terminal).unwrap()
|
||||
}
|
||||
|
||||
fn staged_terminfo_source(&self) -> PathBuf {
|
||||
PathBuf::from(
|
||||
String::from_utf8(
|
||||
self.configuration
|
||||
.runtime_domain_root()
|
||||
.canonical_label()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.join("sandbox-control")
|
||||
.join("11".repeat(32))
|
||||
.join("terminfo/x/xterm-lazaret")
|
||||
}
|
||||
|
||||
fn request(&self, root_process: RootProcess) -> SandboxBuildRequest<'_> {
|
||||
|
|
@ -107,6 +125,16 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
|||
}
|
||||
}
|
||||
|
||||
fn compiled_terminfo(term: &str) -> Vec<u8> {
|
||||
let names = format!("{term}|Lazaret test terminal\0").into_bytes();
|
||||
let mut bytes = Vec::with_capacity(12 + names.len());
|
||||
for value in [0x011a_u16, u16::try_from(names.len()).unwrap(), 0, 0, 0, 0] {
|
||||
bytes.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(&names);
|
||||
bytes
|
||||
}
|
||||
|
||||
fn current_network_namespace() -> NamespaceIdentity {
|
||||
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH)).unwrap()
|
||||
}
|
||||
|
|
@ -255,7 +283,17 @@ fn selected_environment_volume_modes_and_host_terminal_are_preserved() {
|
|||
let terminfo = &mounts["/run/lazaret/terminfo/x/xterm-lazaret"];
|
||||
assert_eq!(
|
||||
terminfo["source"],
|
||||
fixture.terminfo_source.display().to_string()
|
||||
fixture.staged_terminfo_source().display().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read(fixture.staged_terminfo_source()).unwrap(),
|
||||
fs::read(&fixture.terminfo_source).unwrap()
|
||||
);
|
||||
assert!(
|
||||
!bundle
|
||||
.specification_json()
|
||||
.windows(fixture.terminfo_source.as_os_str().as_bytes().len())
|
||||
.any(|window| window == fixture.terminfo_source.as_os_str().as_bytes())
|
||||
);
|
||||
assert!(
|
||||
options(terminfo).is_superset(&BTreeSet::from(["bind", "ro", "nodev", "nosuid", "noexec"]))
|
||||
|
|
@ -289,7 +327,6 @@ fn configuration_document_unselected_secret_and_unselected_host_paths_do_not_ent
|
|||
|
||||
let specification = value(&bundle);
|
||||
let allowed_external_sources = BTreeSet::from([
|
||||
fixture.terminfo_source.display().to_string(),
|
||||
String::from_utf8(
|
||||
fixture
|
||||
.configuration
|
||||
|
|
@ -341,31 +378,34 @@ fn configuration_document_unselected_secret_and_unselected_host_paths_do_not_ent
|
|||
fn hostile_terminal_names_sources_and_changed_entries_fail_closed() {
|
||||
let fixture = Fixture::new();
|
||||
for term in ["../xterm", "xterm/name", "xterm\nname", "", &"x".repeat(65)] {
|
||||
let error = HostTerminal::from_trusted_roots(
|
||||
term,
|
||||
None,
|
||||
&fixture.terminfo_source,
|
||||
std::slice::from_ref(&fixture.terminfo_root),
|
||||
)
|
||||
.unwrap_err();
|
||||
let error =
|
||||
HostTerminal::from_compiled_entry(term, None, compiled_terminfo("xterm-lazaret"))
|
||||
.unwrap_err();
|
||||
assert_eq!(error.kind(), SandboxBuildErrorKind::InvalidTerminal);
|
||||
}
|
||||
let outside = fixture.tree.path().join("outside").join("xterm-lazaret");
|
||||
fs::create_dir(outside.parent().unwrap()).unwrap();
|
||||
fs::write(&outside, b"outside").unwrap();
|
||||
assert!(
|
||||
HostTerminal::from_trusted_roots(
|
||||
"xterm-lazaret",
|
||||
HostTerminal::from_compiled_entry("passwd", None, fs::read("/etc/passwd").unwrap())
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
HostTerminal::from_compiled_entry(
|
||||
"other-terminal",
|
||||
None,
|
||||
&outside,
|
||||
std::slice::from_ref(&fixture.terminfo_root),
|
||||
compiled_terminfo("xterm-lazaret")
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
for invalid in [vec![], b"not compiled terminfo".to_vec(), vec![0_u8; 12]] {
|
||||
assert!(HostTerminal::from_compiled_entry("xterm-lazaret", None, invalid).is_err());
|
||||
}
|
||||
|
||||
let terminal = fixture.terminal();
|
||||
fs::remove_file(&fixture.terminfo_source).unwrap();
|
||||
fs::write(&fixture.terminfo_source, b"replacement").unwrap();
|
||||
fs::remove_file(fixture.staged_terminfo_source()).unwrap();
|
||||
fs::write(
|
||||
fixture.staged_terminfo_source(),
|
||||
compiled_terminfo("xterm-lazaret"),
|
||||
)
|
||||
.unwrap();
|
||||
let error = build_sandbox_bundle(SandboxBuildRequest {
|
||||
configuration: &fixture.configuration,
|
||||
container_id: ContainerId::new([0x11; 32]),
|
||||
|
|
|
|||
Loading…
Reference in a new issue