This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 09:30:21 +02:00
1 parent 1dea7f55c8
commit c819f0a335
151 files changed
+7610 -4264

No files matched your search

+64 -21
View File
@@ -29,7 +29,10 @@ fn backup_input(path: &Path) -> Result<File> {
options.custom_flags(libc::O_NOFOLLOW);
}
let file = options.open(path)?;
ensure!(file.metadata()?.is_file(), "backup input must be a regular file");
ensure!(
file.metadata()?.is_file(),
"backup input must be a regular file"
);
Ok(file)
}
@@ -65,7 +68,9 @@ fn copy_snapshot_file(source: &Path, target: &Path) -> Result<(u64, String)> {
}
output.write_all(&buffer[..read])?;
hasher.update(&buffer[..read]);
size = size.checked_add(read as u64).context("backup input too large")?;
size = size
.checked_add(read as u64)
.context("backup input too large")?;
}
output.sync_all()?;
let after = input.metadata()?;
@@ -82,7 +87,10 @@ fn symlink_target_bytes(target: &Path) -> Result<Vec<u8>> {
{
use std::os::unix::ffi::OsStrExt;
let bytes = target.as_os_str().as_bytes().to_vec();
ensure!(!bytes.is_empty() && bytes.len() <= 4096, "invalid symlink target");
ensure!(
!bytes.is_empty() && bytes.len() <= 4096,
"invalid symlink target"
);
return Ok(bytes);
}
#[cfg(not(unix))]
@@ -92,13 +100,19 @@ fn symlink_target_bytes(target: &Path) -> Result<Vec<u8>> {
.context("symlink target must be valid UTF-8")?
.as_bytes()
.to_vec();
ensure!(!bytes.is_empty() && bytes.len() <= 4096, "invalid symlink target");
ensure!(
!bytes.is_empty() && bytes.len() <= 4096,
"invalid symlink target"
);
Ok(bytes)
}
}
fn symlink_target_path(bytes: &[u8]) -> Result<PathBuf> {
ensure!(!bytes.is_empty() && bytes.len() <= 4096, "invalid symlink target");
ensure!(
!bytes.is_empty() && bytes.len() <= 4096,
"invalid symlink target"
);
#[cfg(unix)]
{
use std::os::unix::ffi::OsStringExt;
@@ -106,22 +120,33 @@ fn symlink_target_path(bytes: &[u8]) -> Result<PathBuf> {
}
#[cfg(not(unix))]
{
Ok(PathBuf::from(std::str::from_utf8(bytes).context("symlink target is not UTF-8")?))
Ok(PathBuf::from(
std::str::from_utf8(bytes).context("symlink target is not UTF-8")?,
))
}
}
fn validate_symlink_target(link_relative: &Path, target: &Path) -> Result<()> {
ensure!(!target.is_absolute(), "backup symlink target must be relative");
let mut depth = link_relative.parent().map_or(0usize, |parent| parent.components().count());
ensure!(
!target.is_absolute(),
"backup symlink target must be relative"
);
let mut depth = link_relative
.parent()
.map_or(0usize, |parent| parent.components().count());
for component in target.components() {
match component {
Component::Normal(_) => depth = depth.checked_add(1).context("symlink target too deep")?,
Component::Normal(_) => {
depth = depth.checked_add(1).context("symlink target too deep")?
}
Component::CurDir => {}
Component::ParentDir => {
ensure!(depth > 0, "backup symlink escapes the data directory");
depth -= 1;
}
Component::RootDir | Component::Prefix(_) => bail!("backup symlink target must be relative"),
Component::RootDir | Component::Prefix(_) => {
bail!("backup symlink target must be relative")
}
}
}
Ok(())
@@ -152,7 +177,10 @@ fn collect_data_files(
let path = item.path();
let name = item.file_name();
let name_text = name.to_string_lossy();
if excluded_file(&name_text) || excluded_directory(&name_text) || path == root.join("secrets") {
if excluded_file(&name_text)
|| excluded_directory(&name_text)
|| path == root.join("secrets")
{
continue;
}
let meta = fs::symlink_metadata(&path)?;
@@ -215,30 +243,45 @@ fn validate_snapshot_symlinks(snapshot_root: &Path, symlinks: &[PathBuf]) -> Res
for link in symlinks {
let resolved = fs::canonicalize(link)
.with_context(|| format!("backup symlink target is missing: {}", link.display()))?;
ensure!(resolved.starts_with(&root), "backup symlink escapes the data directory: {}", link.display());
ensure!(
resolved.starts_with(&root),
"backup symlink escapes the data directory: {}",
link.display()
);
}
Ok(())
}
fn validate_snapshot_config(envelope: &crate::config::Envelope, snapshot_root: &Path) -> Result<()> {
fn validate_snapshot_config(
envelope: &crate::config::Envelope,
snapshot_root: &Path,
) -> Result<()> {
let cert_dir = snapshot_root.join("certs");
for certificate in &envelope.config.tls.certificates {
for name in [&certificate.cert_file, &certificate.key_file] {
crate::tls::safe_name(name)?;
let path = cert_dir.join(name);
let meta = fs::symlink_metadata(&path)
.with_context(|| format!("configured certificate file changed during backup: {name}"))?;
ensure!(meta.is_file() && !meta.file_type().is_symlink(), "configured certificate file is not a regular file: {name}");
let meta = fs::symlink_metadata(&path).with_context(|| {
format!("configured certificate file changed during backup: {name}")
})?;
ensure!(
meta.is_file() && !meta.file_type().is_symlink(),
"configured certificate file is not a regular file: {name}"
);
}
}
if envelope.config.administration.tls && envelope.config.administration.certificate_id.is_none() {
if envelope.config.administration.tls && envelope.config.administration.certificate_id.is_none()
{
for name in ["default.pem", "default.key"] {
let path = cert_dir.join(name);
let meta = fs::symlink_metadata(&path)
.with_context(|| format!("default administration certificate changed during backup: {name}"))?;
ensure!(meta.is_file() && !meta.file_type().is_symlink(), "default administration certificate is not a regular file: {name}");
let meta = fs::symlink_metadata(&path).with_context(|| {
format!("default administration certificate changed during backup: {name}")
})?;
ensure!(
meta.is_file() && !meta.file_type().is_symlink(),
"default administration certificate is not a regular file: {name}"
);
}
}
Ok(())
}