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::{
|
use crate::{
|
||||||
game_paths::{BACKUP_DIR, INSTALL_OWNED_MARKER, INSTALLING_DIR, LOCAL_DIR},
|
game_paths::{BACKUP_DIR, INSTALL_OWNED_MARKER, INSTALLING_DIR, LOCAL_DIR},
|
||||||
|
local_games::read_version_from_ini_bounded,
|
||||||
scoped_blocking::scoped_blocking,
|
scoped_blocking::scoped_blocking,
|
||||||
state_paths::launch_settings_applied_path,
|
state_paths::launch_settings_applied_path,
|
||||||
};
|
};
|
||||||
@@ -549,12 +550,13 @@ async fn unpack_archives(
|
|||||||
/// Windows, any reparse point (junctions, mount points).
|
/// Windows, any reparse point (junctions, mount points).
|
||||||
///
|
///
|
||||||
/// Extraction is delegated to an external `unrar` process whose output is
|
/// Extraction is delegated to an external `unrar` process whose output is
|
||||||
/// otherwise trusted verbatim. The archives themselves are BLAKE3-verified
|
/// otherwise trusted verbatim. Downloaded archives are BLAKE3-verified, but
|
||||||
/// against the bundled catalog, so a link would have to originate from the
|
/// ordinary install intentionally consumes the current root `.eti` set so the
|
||||||
/// catalog publisher; this audit is defense in depth so that a link can never
|
/// S33 local-mutation workflow remains supported. This audit prevents either a
|
||||||
/// redirect later launch-time rewrites (`apply_launch_settings_once`) or an
|
/// catalog archive or a locally supplied archive from redirecting later
|
||||||
/// uninstall outside the game directory. `unrar` is additionally invoked
|
/// launch-time rewrites (`apply_launch_settings_once`) or an uninstall outside
|
||||||
/// with `-ol-` so links are skipped at extraction time.
|
/// 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<()> {
|
fn reject_links_in_staging(staging: &Path) -> eyre::Result<()> {
|
||||||
let staging = staging.to_path_buf();
|
let staging = staging.to_path_buf();
|
||||||
scoped_blocking(move || {
|
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) {
|
if !version_ini_is_regular_file_blocking(game_root) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
match lanspread_db::db::read_version_from_ini(game_root) {
|
match read_version_from_ini_bounded(game_root) {
|
||||||
Ok(version) => version,
|
Ok(version) => version,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!(
|
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 version_path = game_path.join(VERSION_INI);
|
||||||
let Some(bytes) = read_bounded_regular_file(&version_path, MAX_VERSION_INI_BYTES)? else {
|
let Some(bytes) = read_bounded_regular_file(&version_path, MAX_VERSION_INI_BYTES)? else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use tokio_util::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::PeerCtx,
|
context::PeerCtx,
|
||||||
download::open_catalog_file_for_read,
|
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,
|
peer::send_game_file_chunk,
|
||||||
scoped_blocking::scoped_blocking,
|
scoped_blocking::scoped_blocking,
|
||||||
stream_install::{send_game_install_stream, send_stream_install_error},
|
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();
|
let expected_version_for_read = expected_version.clone();
|
||||||
scoped_blocking(move || {
|
scoped_blocking(move || {
|
||||||
expected_version_for_read.as_deref().is_none_or(|expected| {
|
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))
|
.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")
|
std::fs::write(game_root.join("version.ini"), b"20250101")
|
||||||
.expect("sentinel should be restored");
|
.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")
|
std::fs::write(game_root.join("payload.bin"), b"short")
|
||||||
.expect("wrong-sized payload should be written");
|
.expect("wrong-sized payload should be written");
|
||||||
assert_chunk_rejected(&ctx, content_id, &payload, 0, 7).await;
|
assert_chunk_rejected(&ctx, content_id, &payload, 0, 7).await;
|
||||||
|
|||||||
Reference in New Issue
Block a user