fix(peer): bound public readiness sentinel reads
Reuse the stable 64 KiB non-link version.ini reader in anonymous transfer admission and install recovery. A stale Ready snapshot can no longer turn a locally enlarged sentinel into repeated unbounded request-path reads or diagnostic content. Test Plan: - just test - just clippy - oversized public-admission sentinel regression - git diff --check
This commit is contained in:
@@ -26,6 +26,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
game_paths::{BACKUP_DIR, INSTALL_OWNED_MARKER, INSTALLING_DIR, LOCAL_DIR},
|
||||
local_games::read_version_from_ini_bounded,
|
||||
scoped_blocking::scoped_blocking,
|
||||
state_paths::launch_settings_applied_path,
|
||||
};
|
||||
@@ -549,12 +550,13 @@ async fn unpack_archives(
|
||||
/// Windows, any reparse point (junctions, mount points).
|
||||
///
|
||||
/// Extraction is delegated to an external `unrar` process whose output is
|
||||
/// otherwise trusted verbatim. The archives themselves are BLAKE3-verified
|
||||
/// against the bundled catalog, so a link would have to originate from the
|
||||
/// catalog publisher; this audit is defense in depth so that a link can never
|
||||
/// redirect later launch-time rewrites (`apply_launch_settings_once`) or an
|
||||
/// uninstall outside the game directory. `unrar` is additionally invoked
|
||||
/// with `-ol-` so links are skipped at extraction time.
|
||||
/// otherwise trusted verbatim. Downloaded archives are BLAKE3-verified, but
|
||||
/// ordinary install intentionally consumes the current root `.eti` set so the
|
||||
/// S33 local-mutation workflow remains supported. This audit prevents either a
|
||||
/// catalog archive or a locally supplied archive from redirecting later
|
||||
/// launch-time rewrites (`apply_launch_settings_once`) or an uninstall outside
|
||||
/// the game directory. `unrar` is additionally invoked with `-ol-` so links are
|
||||
/// skipped at extraction time.
|
||||
fn reject_links_in_staging(staging: &Path) -> eyre::Result<()> {
|
||||
let staging = staging.to_path_buf();
|
||||
scoped_blocking(move || {
|
||||
@@ -914,7 +916,7 @@ fn read_downloaded_version(game_root: &Path) -> Option<String> {
|
||||
if !version_ini_is_regular_file_blocking(game_root) {
|
||||
return None;
|
||||
}
|
||||
match lanspread_db::db::read_version_from_ini(game_root) {
|
||||
match read_version_from_ini_bounded(game_root) {
|
||||
Ok(version) => version,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
|
||||
@@ -760,7 +760,7 @@ fn build_game_summary(
|
||||
})
|
||||
}
|
||||
|
||||
fn read_version_from_ini_bounded(game_path: &Path) -> eyre::Result<Option<String>> {
|
||||
pub(crate) fn read_version_from_ini_bounded(game_path: &Path) -> eyre::Result<Option<String>> {
|
||||
let version_path = game_path.join(VERSION_INI);
|
||||
let Some(bytes) = read_bounded_regular_file(&version_path, MAX_VERSION_INI_BYTES)? else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -29,7 +29,7 @@ use tokio_util::{
|
||||
use crate::{
|
||||
context::PeerCtx,
|
||||
download::open_catalog_file_for_read,
|
||||
local_games::version_ini_is_regular_file,
|
||||
local_games::{read_version_from_ini_bounded, version_ini_is_regular_file},
|
||||
peer::send_game_file_chunk,
|
||||
scoped_blocking::scoped_blocking,
|
||||
stream_install::{send_game_install_stream, send_stream_install_error},
|
||||
@@ -140,7 +140,7 @@ async fn can_serve_game(ctx: &PeerCtx, game_dir: &std::path::Path, game_id: &str
|
||||
let expected_version_for_read = expected_version.clone();
|
||||
scoped_blocking(move || {
|
||||
expected_version_for_read.as_deref().is_none_or(|expected| {
|
||||
lanspread_db::db::read_version_from_ini(&game_root)
|
||||
read_version_from_ini_bounded(&game_root)
|
||||
.is_ok_and(|version| version.as_deref() == Some(expected))
|
||||
})
|
||||
})
|
||||
@@ -847,6 +847,16 @@ mod tests {
|
||||
std::fs::write(game_root.join("version.ini"), b"20250101")
|
||||
.expect("sentinel should be restored");
|
||||
|
||||
std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(game_root.join("version.ini"))
|
||||
.expect("sentinel should open")
|
||||
.set_len(64 * 1024 + 1)
|
||||
.expect("sentinel should become oversized");
|
||||
assert_chunk_rejected(&ctx, content_id, &payload, 0, 7).await;
|
||||
std::fs::write(game_root.join("version.ini"), b"20250101")
|
||||
.expect("bounded sentinel should be restored");
|
||||
|
||||
std::fs::write(game_root.join("payload.bin"), b"short")
|
||||
.expect("wrong-sized payload should be written");
|
||||
assert_chunk_rejected(&ctx, content_id, &payload, 0, 7).await;
|
||||
|
||||
Reference in New Issue
Block a user