feat(android): implement private Rust storage runtime (#90)
Dependency security audit / rustsec (push) Failing after 23s

This commit is contained in:
hugo committed 2026-10-08 14:36:05 +02:00
1 parent 180851087b
commit 874c8139a4
36 files changed
+2848 -39

No files matched your search

+59 -3
View File
@@ -4,7 +4,7 @@ use std::{
collections::{BTreeMap, BTreeSet},
error::Error,
fmt, fs,
io::Write as _,
io::{Read as _, Write as _},
path::Path,
sync::{
Mutex,
@@ -12,8 +12,8 @@ use std::{
},
};
use crate::atomic_file::TempFile;
use cap_std::{ambient_authority, fs::Dir};
use cap_tempfile::TempFile;
use data_encoding::HEXLOWER;
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};
@@ -27,6 +27,7 @@ use crate::{
};
const CACHE_VERSION: u32 = 1;
const MAX_CATALOG_BYTES: u64 = 32 * 1024 * 1024;
// ponytail: cache writes are rare; use per-cache locks only if profiling shows contention.
static CACHE_WRITE_LOCK: Mutex<()> = Mutex::new(());
@@ -613,7 +614,9 @@ fn inspect_entry(
fn load_cache(path: &Path, store: &str) -> (Option<CachedCatalog>, Option<String>) {
if let Ok(metadata) = fs::symlink_metadata(path)
&& (metadata.file_type().is_symlink() || !metadata.is_file())
&& (metadata.file_type().is_symlink()
|| !metadata.is_file()
|| metadata.len() > MAX_CATALOG_BYTES)
{
return (
None,
@@ -642,6 +645,59 @@ fn load_cache(path: &Path, store: &str) -> (Option<CachedCatalog>, Option<String
}
}
pub(crate) fn validate_private_catalog(
root: &Path,
path: &Path,
) -> Result<bool, crate::mobile_private_storage::PrivateStorageError> {
use crate::mobile_private_storage::PrivateStorageError;
let _lock = CACHE_WRITE_LOCK
.lock()
.map_err(|_| PrivateStorageError::Io)?;
let metadata = match fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(error.into()),
};
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(PrivateStorageError::UnsafePath);
}
let store = store_identity(root, path);
let parent = Dir::open_ambient_dir(
path.parent().ok_or(PrivateStorageError::UnsafePath)?,
ambient_authority(),
)?;
let name = path.file_name().ok_or(PrivateStorageError::UnsafePath)?;
let file = parent.open(name)?;
crate::mobile_private_storage::private_permissions(&file, false)?;
let mut bytes = Vec::new();
if metadata.len() <= MAX_CATALOG_BYTES {
file.take(MAX_CATALOG_BYTES + 1).read_to_end(&mut bytes)?;
}
let source = std::str::from_utf8(&bytes).ok();
let valid = metadata.len() <= MAX_CATALOG_BYTES
&& bytes.len() as u64 <= MAX_CATALOG_BYTES
&& source
.and_then(|source| toml::from_str::<CachedCatalog>(source).ok())
.is_some_and(|catalog| {
catalog.version == CACHE_VERSION
&& catalog.store == store
&& catalog.entries.iter().all(|(entry, record)| {
EntryPath::parse(entry).is_ok()
&& record.ciphertext_hash.len() == 64
&& record
.ciphertext_hash
.bytes()
.all(|byte| byte.is_ascii_hexdigit())
})
});
if valid {
return Ok(false);
}
parent.remove_file(name)?;
parent.open(".")?.sync_all()?;
Ok(true)
}
fn upsert_cache_record(
path: &Path,
store: &str,