Descriptor-anchored Secured Filesystem Operations

This commit is contained in:
Alexey
2026-09-17 22:45:48 +03:00
parent 9a683d8b3d
commit 02f66c542e
41 changed files with 2997 additions and 1759 deletions
+26 -13
View File
@@ -12,6 +12,8 @@ use tracing::{debug, info, warn};
use crate::config::ProxyConfig;
use crate::error::Result;
use crate::transport::UpstreamManager;
#[cfg(unix)]
use crate::util::secure_fs::{atomic_replace_async, read_regular_limited_async};
use super::MePool;
use super::http_fetch::{HTTPS_RESPONSE_BODY_MAX_BYTES, https_get};
@@ -73,25 +75,36 @@ pub fn parse_proxy_config_text(text: &str, http_status: u16) -> ProxyConfigData
}
pub async fn load_proxy_config_cache(path: &str) -> Result<ProxyConfigData> {
let text = tokio::fs::read_to_string(path).await.map_err(|e| {
#[cfg(unix)]
let bytes = read_regular_limited_async(
Path::new(path).to_path_buf(),
HTTPS_RESPONSE_BODY_MAX_BYTES,
)
.await;
#[cfg(not(unix))]
let bytes = tokio::fs::read(path).await;
let bytes = bytes.map_err(|e| {
crate::error::ProxyError::Proxy(format!("read proxy-config cache '{path}' failed: {e}"))
})?;
let text = String::from_utf8(bytes).map_err(|e| {
crate::error::ProxyError::Proxy(format!(
"proxy-config cache '{path}' is not valid UTF-8: {e}"
))
})?;
Ok(parse_proxy_config_text(&text, 200))
}
pub async fn save_proxy_config_cache(path: &str, raw_text: &str) -> Result<()> {
if let Some(parent) = Path::new(path).parent()
&& !parent.as_os_str().is_empty()
{
tokio::fs::create_dir_all(parent).await.map_err(|e| {
crate::error::ProxyError::Proxy(format!(
"create proxy-config cache dir '{}' failed: {e}",
parent.display()
))
})?;
}
tokio::fs::write(path, raw_text).await.map_err(|e| {
#[cfg(unix)]
let write = atomic_replace_async(
Path::new(path).to_path_buf(),
raw_text.as_bytes().to_vec(),
0o640,
)
.await;
#[cfg(not(unix))]
let write = tokio::fs::write(path, raw_text).await;
write.map_err(|e| {
crate::error::ProxyError::Proxy(format!("write proxy-config cache '{path}' failed: {e}"))
})?;
Ok(())
+14 -2
View File
@@ -1,4 +1,5 @@
use httpdate;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;
use tracing::{debug, info, warn};
@@ -7,6 +8,8 @@ use super::http_fetch::https_get;
use super::selftest::record_timeskew_sample;
use crate::error::{ProxyError, Result};
use crate::transport::UpstreamManager;
#[cfg(unix)]
use crate::util::secure_fs::{atomic_replace_async, read_regular_limited_async};
pub const PROXY_SECRET_MIN_LEN: usize = 32;
@@ -58,7 +61,12 @@ pub async fn fetch_proxy_secret_with_upstream(
match download_proxy_secret_with_max_len_via_upstream(max_len, upstream, proxy_secret_url).await
{
Ok(data) => {
if let Err(e) = tokio::fs::write(cache, &data).await {
#[cfg(unix)]
let cache_result =
atomic_replace_async(PathBuf::from(cache), data.clone(), 0o600).await;
#[cfg(not(unix))]
let cache_result = tokio::fs::write(cache, &data).await;
if let Err(e) = cache_result {
warn!(error = %e, "Failed to cache proxy-secret (non-fatal)");
} else {
debug!(path = cache, len = data.len(), "Cached proxy-secret");
@@ -72,7 +80,11 @@ pub async fn fetch_proxy_secret_with_upstream(
}
// 2) Fallback to cache/file regardless of age; require len in bounds.
match tokio::fs::read(cache).await {
#[cfg(unix)]
let cached = read_regular_limited_async(PathBuf::from(cache), max_len).await;
#[cfg(not(unix))]
let cached = tokio::fs::read(cache).await;
match cached {
Ok(data) if validate_proxy_secret_len(data.len(), max_len).is_ok() => {
let age_hours = tokio::fs::metadata(cache)
.await