mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
keys: refuse unparseable FMTS segment table; cap keys per source answer
This commit is contained in:
+3
-2
@@ -31,7 +31,7 @@ pub(crate) struct Layout {
|
||||
// Bytes per source packet (the SPN unit).
|
||||
const SPN_BYTES: u64 = 192;
|
||||
|
||||
/// Read the disc's forensic layout. `Ok(None)`: not FMTS (no or empty segment table).
|
||||
/// Read the disc's forensic layout. `Ok(None)`: not FMTS (no or empty segment table); an unparseable table is refused.
|
||||
pub(crate) fn layout(
|
||||
fs: &crate::udf::UdfFs,
|
||||
reader: &mut dyn SectorSource,
|
||||
@@ -43,7 +43,8 @@ pub(crate) fn layout(
|
||||
};
|
||||
let Some(segments) = parse_individual_segments(&tbl) else {
|
||||
tracing::warn!(target: "freemkv::keys", "fmts: IndividualSegment.tbl present but unparseable");
|
||||
return Ok(None);
|
||||
// Ok(None) would rip forensic units as base content: refuse.
|
||||
return Err(Error::FmtsKeyMissing);
|
||||
};
|
||||
if segments.is_empty() {
|
||||
return Ok(None);
|
||||
|
||||
+6
-6
@@ -27,7 +27,8 @@ const RETRY_CAP: Duration = Duration::from_secs(8);
|
||||
const NO_ANSWER_WINDOW: Duration = Duration::from_secs(60);
|
||||
/// Encrypted units sent in one per-piece request, at most.
|
||||
const SAMPLE_CAP: usize = 32;
|
||||
// Most keys a run holds: real discs declare few CPS units; a service cannot flood the pool.
|
||||
// Most keys taken from one source answer: real discs declare few CPS units; a source cannot
|
||||
// flood the pool or starve a later source.
|
||||
const MAX_POOL_KEYS: usize = 256;
|
||||
|
||||
/// Time for the J13 retry: injected so tests use a fake clock and never sleep.
|
||||
@@ -145,11 +146,10 @@ impl Run<'_> {
|
||||
|
||||
// Add keys to the pool (deduplicated): the pool only grows during `resolve`.
|
||||
fn add_keys(&mut self, keys: &[[u8; 16]], who: &'static str) {
|
||||
for k in keys {
|
||||
if self.pool.len() >= MAX_POOL_KEYS {
|
||||
tracing::warn!(target: "freemkv::keys", who, "key pool full: extra keys ignored");
|
||||
break;
|
||||
}
|
||||
if keys.len() > MAX_POOL_KEYS {
|
||||
tracing::warn!(target: "freemkv::keys", who, "source answer truncated: extra keys ignored");
|
||||
}
|
||||
for k in keys.iter().take(MAX_POOL_KEYS) {
|
||||
if !self.pool.contains(k) {
|
||||
self.pool.push(*k);
|
||||
self.origin.push(who);
|
||||
|
||||
@@ -1502,6 +1502,23 @@ fn fmts_fixture() -> Fx {
|
||||
fx
|
||||
}
|
||||
|
||||
/// A present but unparseable segment table is a refusal, never "not FMTS" (which would rip
|
||||
/// forensic units as base content).
|
||||
#[test]
|
||||
fn fmts_malformed_segment_table_is_refused() {
|
||||
let mut fx = fmts_fixture();
|
||||
let at = fx.file(2).0 as usize * 2048;
|
||||
// record_size (bytes 6..8) no longer matches the record length.
|
||||
fx.img.image[at + 6..at + 8].copy_from_slice(&15u16.to_be_bytes());
|
||||
let calls = Calls::default();
|
||||
let r = resolve(
|
||||
&fx,
|
||||
KeyScope::Titles(vec![2]),
|
||||
&[Spec::keydb(&[K1, K2], &calls)],
|
||||
);
|
||||
assert!(matches!(r, Err(Error::FmtsKeyMissing)), "{:?}", r.err());
|
||||
}
|
||||
|
||||
fn fmts_online(calls: &Calls) -> Spec {
|
||||
let mut s = Spec::online(&[K2], calls);
|
||||
s.fmts = vec![F1, F2];
|
||||
@@ -2784,6 +2801,40 @@ fn pieces_survive_extents_near_the_lba_limit() {
|
||||
assert!(!spans.is_empty());
|
||||
}
|
||||
|
||||
/// The push-closure path (a title extent no file covers) survives an extent near u32::MAX.
|
||||
#[test]
|
||||
fn uncovered_title_extent_near_the_lba_limit_survives() {
|
||||
let mut fx = two_units();
|
||||
fx.disc.titles[0].extents = vec![Extent {
|
||||
start_lba: u32::MAX - 4,
|
||||
sector_count: 10,
|
||||
}];
|
||||
let spans = crate::keys::resolve::whole_disc_pieces(&fx.disc, &[]);
|
||||
assert!(!spans.is_empty());
|
||||
}
|
||||
|
||||
/// One source flooding the pool cannot starve a later source's real key.
|
||||
#[test]
|
||||
fn a_flooding_source_does_not_starve_a_later_source() {
|
||||
let fx = two_units();
|
||||
let junk: Vec<[u8; 16]> = (0..300u32)
|
||||
.map(|i| {
|
||||
let mut k = [0u8; 16];
|
||||
k[..4].copy_from_slice(&(i + 1).to_be_bytes());
|
||||
k
|
||||
})
|
||||
.collect();
|
||||
let calls = Calls::default();
|
||||
let mut later = Spec::keydb(&[K1, K2], &calls);
|
||||
later.who = "later";
|
||||
let r = resolve(
|
||||
&fx,
|
||||
KeyScope::WholeDisc,
|
||||
&[Spec::keydb(&junk, &calls), later],
|
||||
);
|
||||
assert!(r.is_ok(), "{:?}", r.err());
|
||||
}
|
||||
|
||||
/// A key service cannot flood the pool: keys past the cap are ignored.
|
||||
#[test]
|
||||
fn key_pool_is_capped() {
|
||||
|
||||
Reference in New Issue
Block a user