fix(ld): bind drives whose reported product_id differs from the generic catalog id

find_by_drive_id gated the product-id-blind four-field fallback behind an empty
product_id in 1.7.0, so a drive reporting a specific product_id (e.g. an LG BU40N
reporting "BD-RE BU40N") no longer matched its catalog entry (generic "BD-RE"),
silently skipping the LibreDrive firmware unlocker — the rip fell through to the
AACS host-cert path (LD: no, AACS: yes).

Run the four-field fallback (vendor/revision/vendor_specific/firmware_date)
unconditionally but ambiguity-guarded: bind a UNIQUE catalog match regardless of
the reported product_id (the real-drive case), and require the exact product_id
only when catalogued siblings genuinely share the four-field identity — preserving
the anti-mis-bind protection the 1.7.0 gate intended. Regression test added.

Bump to 1.7.1.
This commit is contained in:
Matthew Jackson
2026-09-13 18:09:50 -07:00
parent 199a3ffee6
commit 407c7f59e3
3 changed files with 81 additions and 19 deletions
Generated
+1 -1
View File
@@ -142,7 +142,7 @@ dependencies = [
[[package]] [[package]]
name = "freemkv-unlock" name = "freemkv-unlock"
version = "1.7.0" version = "1.7.1"
dependencies = [ dependencies = [
"aes", "aes",
"base64", "base64",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "freemkv-unlock" name = "freemkv-unlock"
version = "1.7.0" version = "1.7.1"
edition = "2024" edition = "2024"
rust-version = "1.88" rust-version = "1.88"
license = "MIT" license = "MIT"
+79 -17
View File
@@ -309,13 +309,19 @@ fn load_from_str(data: &str) -> Result<Profiles> {
/// Find a profile matching a drive's INQUIRY fields. /// Find a profile matching a drive's INQUIRY fields.
/// ///
/// Per platform section (MT1959-A, then MT1959-B, then Renesas). A drive that /// Per platform section (MT1959-A, then MT1959-B, then Renesas), two passes:
/// reports a `product_id` matches ONLY on the full identity including it — if ///
/// no cataloged profile matches exactly, the drive is treated as uncataloged /// 1. Exact match including `product_id` (only when the drive reports one).
/// (no match), never bound to a same-vendor/revision sibling. The looser /// 2. Product-id-blind fallback on the four-field identity
/// four-field pass (vendor/revision/vendor_specific/firmware_date) runs only /// (vendor/revision/vendor_specific/firmware_date). Catalogs store a GENERIC
/// when the drive reports no `product_id` at all. All comparisons are /// `product_id` (e.g. "BD-RE") while drives report a specific one (e.g.
/// whitespace-trimmed. Returns the first section that matches. /// "BD-RE BU40N"), so pass 1 legitimately misses on real drives. Pass 2 binds
/// a UNIQUE four-field match regardless of the reported product_id; when two
/// cataloged siblings share the four-tuple (differing only by product_id) it
/// binds only if the drive reported no product_id to disambiguate with —
/// otherwise the drive is uncataloged and must not mis-bind to a sibling.
///
/// All comparisons are whitespace-trimmed. Returns the first section that matches.
pub fn find_by_drive_id(profiles: &Profiles, drive_id: &crate::DriveId) -> Option<ProfileMatch> { pub fn find_by_drive_id(profiles: &Profiles, drive_id: &crate::DriveId) -> Option<ProfileMatch> {
let v = drive_id.vendor_id.trim(); let v = drive_id.vendor_id.trim();
let prod = drive_id.product_id.trim(); let prod = drive_id.product_id.trim();
@@ -343,21 +349,46 @@ pub fn find_by_drive_id(profiles: &Profiles, drive_id: &crate::DriveId) -> Optio
}); });
} }
// The product_id-blind pass runs ONLY when the drive reports none: a // Product-id-blind fallback. The catalog stores a GENERIC product_id
// drive that reports a product_id but misses the exact match is an // (e.g. "BD-RE"), while a drive reports a specific one (e.g. "BD-RE
// uncataloged variant, else it'd bind to a sibling's wrong firmware. // BU40N"), so the exact pass above legitimately misses on real drives.
if prod.is_empty() // Fall back to the four-field identity
&& let Some(p) = list.iter().find(|p| { // (vendor/revision/vendor_specific/firmware_date), which is unique per
// profile — but guard against MIS-BINDING: when two cataloged siblings
// share the four-tuple and differ only by product_id, a drive whose
// reported product_id matched neither (above) is an uncataloged variant
// and must NOT bind to the wrong sibling's firmware.
let four_field: Vec<&DriveProfile> = list
.iter()
.filter(|p| {
p.identity.vendor_id.trim() == v p.identity.vendor_id.trim() == v
&& p.identity.product_revision.trim() == r && p.identity.product_revision.trim() == r
&& p.identity.vendor_specific.trim() == vs && p.identity.vendor_specific.trim() == vs
&& p.identity.firmware_date.trim() == date && p.identity.firmware_date.trim() == date
}) })
{ .collect();
return Some(ProfileMatch { match four_field.as_slice() {
profile: p.clone(), // Exactly one four-field match — unambiguous, so bind it regardless
platform, // of the reported product_id. This is the common real-drive case (a
}); // generic cataloged product_id vs a specific reported one) and the
// pre-1.7.0 behaviour a UHD-capable LG BU40N relied on.
[only] => {
return Some(ProfileMatch {
profile: (*only).clone(),
platform,
});
}
// Two-plus cataloged siblings share the four-tuple. Only bind when
// the drive reported NO product_id to disambiguate with; a non-empty
// product_id that missed the exact pass is an uncataloged variant and
// must not mis-bind to a sibling's firmware.
[first, ..] if prod.is_empty() => {
return Some(ProfileMatch {
profile: (*first).clone(),
platform,
});
}
_ => {}
} }
} }
@@ -427,6 +458,37 @@ mod tests {
); );
} }
/// REGRESSION (1.7.0): a real drive reports a SPECIFIC product_id (e.g.
/// "BD-RE BU40N") while the catalog stores a GENERIC one ("BD-RE"), so the
/// exact pass misses. When the four-field identity is UNIQUE it must still
/// bind — the pre-1.7.0 behaviour a UHD LG BU40N relies on for LibreDrive
/// unlock. 1.7.0 gated the four-field pass behind an empty product_id, so it
/// silently stopped matching and the firmware unlocker was skipped entirely.
#[test]
fn find_by_drive_id_specific_product_id_binds_unique_four_field() {
use serde_json::json;
let profiles: Profiles = serde_json::from_str(
&json!({
"mt1959_a": [
{"identity": {"vendor_id":"HL-DT-ST","product_id":"BD-RE",
"product_revision":"1.03","vendor_specific":"NM00000",
"firmware_date":"211810241934"},
"signature":"12345678","firmware":""}
]
})
.to_string(),
)
.unwrap();
// Drive reports the specific marketing product_id; catalog has "BD-RE".
let mut id = make_drive_id("HL-DT-ST", "1.03", "NM00000", "211810241934");
id.product_id = "BD-RE BU40N".to_string();
let m = find_by_drive_id(&profiles, &id)
.expect("unique four-field identity must match despite the product_id mismatch");
assert_eq!(m.profile.signature, [0x12, 0x34, 0x56, 0x78]);
assert_eq!(m.platform, Platform::Mt1959A);
}
#[test] #[test]
fn test_find_known_drive() { fn test_find_known_drive() {
let profiles = load_bundled().unwrap(); let profiles = load_bundled().unwrap();