Skip to content

Commit 4b60f3f

Browse files
committed
🐛 fix(backup): confine member access
Hold the backup root as a directory capability and resolve each manifest member through descriptor-relative, no-follow opens. Verification and restore reject traversal and symlink swaps through the shared boundary. Closes #1181
1 parent 60698d6 commit 4b60f3f

12 files changed

Lines changed: 449 additions & 108 deletions

File tree

Cargo.lock

Lines changed: 109 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ aws-config = { version = "1.6", default-features = false, features = ["behavior-
7777
aws-sdk-s3 = { version = "1.144", default-features = false, features = ["behavior-version-latest", "default-https-client", "http-1x", "rt-tokio"] }
7878
aws-smithy-http-client = { version = "1.4", default-features = false, features = ["test-util"] }
7979
bytes = "1.12.1"
80+
cap-fs-ext = "4.0.3"
81+
cap-std = "4.0.3"
8082
httpdate = "1"
8183
futures-core = "0.3.34"
8284
futures-util = "0.3"

crates/peryx/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ sha2.workspace = true
7777
thiserror.workspace = true
7878
time.workspace = true
7979
anyhow.workspace = true
80+
cap-fs-ext.workspace = true
81+
cap-std.workspace = true
8082
tracing.workspace = true
8183
tracing-subscriber.workspace = true
8284
tracing-appender.workspace = true

crates/peryx/src/metadata.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fs::File;
12
use std::ops::Deref;
23
use std::path::Path;
34

@@ -40,8 +41,9 @@ pub fn open_existing_read_only(path: &Path, plugins: &PluginRegistry) -> anyhow:
4041
Ok(store)
4142
}
4243

43-
pub fn open_existing_copy(path: &Path, plugins: &PluginRegistry) -> anyhow::Result<OpenedMetadata> {
44-
let source = MetaStore::open_existing_read_only(path)
44+
pub fn open_existing_copy(source: File, path: &Path, plugins: &PluginRegistry) -> anyhow::Result<OpenedMetadata> {
45+
let probe = Probe::copy(source, path)?;
46+
let source = MetaStore::open_existing_read_only(&probe.path)
4547
.with_context(|| format!("open metadata store {} read-only", path.display()))?;
4648
if !source
4749
.user_names_require_migration()
@@ -50,17 +52,13 @@ pub fn open_existing_copy(path: &Path, plugins: &PluginRegistry) -> anyhow::Resu
5052
{
5153
return Ok(OpenedMetadata {
5254
store: source,
53-
_probe: None,
55+
_probe: probe,
5456
});
5557
}
56-
let probe = Probe::copy(path)?;
5758
drop(source);
5859
let store = MetaStore::open_existing(&probe.path).context("open copied metadata store")?;
5960
let store = migrate(store, plugins)?;
60-
Ok(OpenedMetadata {
61-
store,
62-
_probe: Some(probe),
63-
})
61+
Ok(OpenedMetadata { store, _probe: probe })
6462
}
6563

6664
fn migrate(store: MetaStore, plugins: &PluginRegistry) -> anyhow::Result<MetaStore> {
@@ -75,7 +73,7 @@ struct Probe {
7573

7674
pub struct OpenedMetadata {
7775
store: MetaStore,
78-
_probe: Option<Probe>,
76+
_probe: Probe,
7977
}
8078

8179
impl Deref for OpenedMetadata {
@@ -87,19 +85,9 @@ impl Deref for OpenedMetadata {
8785
}
8886

8987
impl Probe {
90-
fn copy(source: &Path) -> anyhow::Result<Self> {
91-
let current_directory = Path::new(".");
92-
let parent = source
93-
.parent()
94-
.filter(|path| !path.as_os_str().is_empty())
95-
.unwrap_or(current_directory);
96-
let mut probe = NamedTempFile::with_prefix_in(".peryx-metadata-probe-", parent)
97-
.context(format!("create metadata schema probe beside {}", source.display()))?;
98-
std::io::copy(
99-
&mut std::fs::File::open(source).context("open metadata store for schema inspection")?,
100-
probe.as_file_mut(),
101-
)
102-
.context("copy metadata store for schema inspection")?;
88+
fn copy(mut source: File, path: &Path) -> anyhow::Result<Self> {
89+
let mut probe = NamedTempFile::new().context(format!("create metadata schema probe for {}", path.display()))?;
90+
std::io::copy(&mut source, probe.as_file_mut()).context("copy metadata store for schema inspection")?;
10391
Ok(Self {
10492
path: probe.into_temp_path(),
10593
})

0 commit comments

Comments
 (0)