Skip to content

Commit

Permalink
Fix handling of invalid registry dword entries
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Dec 18, 2023
1 parent 9909c7b commit 8d74fbc
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 10 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
* CLI: `wrap` command to do a restore before playing a game and a backup afterwards.
([Contributed by sluedecke](https://github.com/mtkennerly/ludusavi/pull/235))
* When a path or URL fails to open, additional information is now logged.
* On Windows, Ludusavi can now back up additional types of registry data:
`REG_NONE`,
`REG_DWORD_BIG_ENDIAN`,
`REG_LINK`,
`REG_RESOURCE_LIST`,
`REG_FULL_RESOURCE_DESCRIPTOR`,
`REG_RESOURCE_REQUIREMENTS_LIST`.
* Changed:
* GUI: A different icon is now used for the button to hide the backup comment field.
The previous icon (a red X) could have been misinterpreted as "delete" rather than "close".
Expand All @@ -13,6 +20,10 @@
* When storing file modified times in zip archives,
if the year is too old for zip to support (i.e., before 1980),
Ludusavi will now round up to the earliest support date (1980-01-01).
* When backing up a malformed `dword`-type value from the registry,
Ludusavi would silently convert it to a default 0,
which could result in data loss when restored.
Now, invalid registry values are backed up and restored as-is.

## v0.21.0 (2023-08-22)

Expand Down
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ whoami = "1.2.1"
zip = "0.6.2"

[target.'cfg(windows)'.dependencies]
winreg = "0.10.1"
winreg = "0.14.0"
winapi = { version = "0.3.9", features = ["wincon"], default-features = false }

[target.'cfg(windows)'.build-dependencies]
Expand Down
11 changes: 10 additions & 1 deletion src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,8 @@ mod tests {
.with_value_new("multiSz")
.with_value_new("qword")
.with_value_new("sz"),
ScannedRegistry::new("HKEY_CURRENT_USER/Software/Ludusavi/invalid").change_as(ScanChange::New)
.with_value_new("dword"),
ScannedRegistry::new("HKEY_CURRENT_USER/Software/Ludusavi/other").change_as(ScanChange::New),
},
..Default::default()
Expand Down Expand Up @@ -1273,7 +1275,10 @@ mod tests {
let cases = vec![
(
BackupFilter {
ignored_registry: vec![RegistryItem::new(s("HKEY_CURRENT_USER\\Software/Ludusavi/other"))],
ignored_registry: vec![
RegistryItem::new(s("HKEY_CURRENT_USER\\Software/Ludusavi/invalid")),
RegistryItem::new(s("HKEY_CURRENT_USER\\Software/Ludusavi/other")),
],
..Default::default()
},
ToggledRegistry::default(),
Expand Down Expand Up @@ -1304,6 +1309,8 @@ mod tests {
.with_value("multiSz", ScanChange::New, true)
.with_value("qword", ScanChange::New, true)
.with_value("sz", ScanChange::New, true),
ScannedRegistry::new("HKEY_CURRENT_USER/Software/Ludusavi/invalid").ignored().change_as(ScanChange::New)
.with_value("dword", ScanChange::New, true),
ScannedRegistry::new("HKEY_CURRENT_USER/Software/Ludusavi/other").ignored().change_as(ScanChange::New),
},
),
Expand All @@ -1329,6 +1336,8 @@ mod tests {
.with_value_new("multiSz")
.with_value("qword", ScanChange::New, true)
.with_value_new("sz"),
ScannedRegistry::new("HKEY_CURRENT_USER/Software/Ludusavi/invalid").change_as(ScanChange::New)
.with_value_new("dword"),
ScannedRegistry::new("HKEY_CURRENT_USER/Software/Ludusavi/other").ignored().change_as(ScanChange::New),
},
),
Expand Down
127 changes: 121 additions & 6 deletions src/scan/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,71 @@ pub enum Entry {
Qword(u64),
#[serde(rename = "binary")]
Binary(Vec<u8>),
#[serde(rename = "raw")]
Raw { kind: RegistryKind, data: Vec<u8> },
#[default]
#[serde(other)]
Unknown,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RegistryKind {
#[default]
None,
Sz,
ExpandSz,
Binary,
Dword,
DwordBigEndian,
Link,
MultiSz,
ResourceList,
FullResourceDescriptor,
ResourceRequirementsList,
Qword,
}

impl From<winreg::enums::RegType> for RegistryKind {
fn from(value: winreg::enums::RegType) -> Self {
use winreg::enums::*;

match value {
REG_NONE => Self::None,
REG_SZ => Self::Sz,
REG_EXPAND_SZ => Self::ExpandSz,
REG_BINARY => Self::Binary,
REG_DWORD => Self::Dword,
REG_DWORD_BIG_ENDIAN => Self::DwordBigEndian,
REG_LINK => Self::Link,
REG_MULTI_SZ => Self::MultiSz,
REG_RESOURCE_LIST => Self::ResourceList,
REG_FULL_RESOURCE_DESCRIPTOR => Self::FullResourceDescriptor,
REG_RESOURCE_REQUIREMENTS_LIST => Self::ResourceRequirementsList,
REG_QWORD => Self::Qword,
}
}
}

impl From<RegistryKind> for winreg::enums::RegType {
fn from(value: RegistryKind) -> Self {
match value {
RegistryKind::None => Self::REG_NONE,
RegistryKind::Sz => Self::REG_SZ,
RegistryKind::ExpandSz => Self::REG_EXPAND_SZ,
RegistryKind::Binary => Self::REG_BINARY,
RegistryKind::Dword => Self::REG_DWORD,
RegistryKind::DwordBigEndian => Self::REG_DWORD_BIG_ENDIAN,
RegistryKind::Link => Self::REG_LINK,
RegistryKind::MultiSz => Self::REG_MULTI_SZ,
RegistryKind::ResourceList => Self::REG_RESOURCE_LIST,
RegistryKind::FullResourceDescriptor => Self::REG_FULL_RESOURCE_DESCRIPTOR,
RegistryKind::ResourceRequirementsList => Self::REG_RESOURCE_REQUIREMENTS_LIST,
RegistryKind::Qword => Self::REG_QWORD,
}
}
}

pub fn scan_registry(
game: &str,
path: &str,
Expand Down Expand Up @@ -345,14 +405,28 @@ impl Entry {

impl From<winreg::RegValue> for Entry {
fn from(item: winreg::RegValue) -> Self {
macro_rules! map {
($variant:expr, $typ:ty) => {
<$typ>::from_reg_value(&item)
.map($variant)
.unwrap_or_else(|_| Self::Raw {
kind: item.vtype.into(),
data: item.bytes,
})
};
}

match item.vtype {
winreg::enums::RegType::REG_SZ => Self::Sz(String::from_reg_value(&item).unwrap_or_default()),
winreg::enums::RegType::REG_EXPAND_SZ => Self::ExpandSz(String::from_reg_value(&item).unwrap_or_default()),
winreg::enums::RegType::REG_MULTI_SZ => Self::MultiSz(String::from_reg_value(&item).unwrap_or_default()),
winreg::enums::RegType::REG_DWORD => Self::Dword(u32::from_reg_value(&item).unwrap_or_default()),
winreg::enums::RegType::REG_QWORD => Self::Qword(u64::from_reg_value(&item).unwrap_or_default()),
winreg::enums::RegType::REG_SZ => map!(Self::Sz, String),
winreg::enums::RegType::REG_EXPAND_SZ => map!(Self::ExpandSz, String),
winreg::enums::RegType::REG_MULTI_SZ => map!(Self::MultiSz, String),
winreg::enums::RegType::REG_DWORD => map!(Self::Dword, u32),
winreg::enums::RegType::REG_QWORD => map!(Self::Qword, u64),
winreg::enums::RegType::REG_BINARY => Self::Binary(item.bytes),
_ => Default::default(),
_ => Self::Raw {
kind: item.vtype.into(),
data: item.bytes,
},
}
}
}
Expand All @@ -375,6 +449,10 @@ impl From<&Entry> for Option<winreg::RegValue> {
bytes: x.clone(),
vtype: winreg::enums::RegType::REG_BINARY,
}),
Entry::Raw { kind, data } => Some(winreg::RegValue {
bytes: data.clone(),
vtype: (*kind).into(),
}),
Entry::Unknown => None,
}
}
Expand Down Expand Up @@ -419,6 +497,24 @@ mod tests {
);
}

#[test]
fn can_store_key_from_full_path_of_leaf_key_with_invalid_values() {
let mut hives = Hives::default();
hives
.store_key_from_full_path("HKEY_CURRENT_USER/Software/Ludusavi/invalid")
.unwrap();
assert_eq!(
Hives(hashmap! {
s("HKEY_CURRENT_USER") => Keys(hashmap! {
s("Software\\Ludusavi\\invalid") => Entries(hashmap! {
s("dword") => Entry::Raw { kind: RegistryKind::Dword, data: vec![0, 0, 0, 0, 0, 0, 0, 0] },
})
})
}),
hives,
);
}

#[test]
fn can_store_key_from_full_path_of_leaf_key_without_values() {
let mut hives = Hives::default();
Expand Down Expand Up @@ -474,6 +570,19 @@ HKEY_CURRENT_USER:
qword: 2
sz:
sz: foo
"Software\\Ludusavi\\invalid":
dword:
raw:
kind: dword
data:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
"Software\\Ludusavi\\other": {}
"#
.trim(),
Expand All @@ -488,6 +597,12 @@ HKEY_CURRENT_USER:
s("qword") => Entry::Qword(2),
s("binary") => Entry::Binary(vec![1, 2, 3]),
}),
s("Software\\Ludusavi\\invalid") => Entries(hashmap! {
s("dword") => Entry::Raw {
kind: RegistryKind::Dword,
data: vec![0, 0, 0, 0, 0, 0, 0, 0],
},
}),
s("Software\\Ludusavi\\other") => Entries::default(),
})
}))
Expand Down
3 changes: 3 additions & 0 deletions tests/ludusavi.reg
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Ludusavi\other]

[HKEY_CURRENT_USER\Software\Ludusavi\invalid]
"dword"=hex(4):00,00,00,00,00,00,00,00

[HKEY_CURRENT_USER\Software\Ludusavi\sp/ecial]
"va/lu\\e"=""

0 comments on commit 8d74fbc

Please sign in to comment.