Skip to content

Commit 9950835

Browse files
committed
fix(patch): reject POSIX-style absolute paths in archive entries on Windows
`read_archive_to_map` rejects entries whose path is absolute or contains a `..` component, but the check used `Path::is_absolute()` alone. On Windows that function requires a drive letter or UNC prefix, so a tar entry like `/etc/passwd` is NOT considered absolute and would slip through the guard — when later joined to the target directory, Windows would treat it as relative to the current drive's root. Add an explicit check for a leading `/` or `\` byte alongside `Path::is_absolute()` so the guard rejects POSIX-style absolute paths on every platform. The new test_read_archive_rejects_backslash_absolute_paths case locks the symmetric backslash form in. This was uncovered when the CI matrix was extended to actually run on Windows. The existing test_read_archive_rejects_absolute_paths failed on windows-latest because it constructed the archive with a POSIX-style path that the platform-specific `is_absolute()` did not catch. Assisted-by: Claude Code:claude-opus-4-7
1 parent 99f5fc6 commit 9950835

1 file changed

Lines changed: 23 additions & 0 deletions

File tree

crates/socket-patch-core/src/patch/package.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,18 @@ pub fn read_archive_to_map(archive_path: &Path) -> Result<HashMap<String, Vec<u8
9494
let path_str = path.to_string_lossy().to_string();
9595

9696
// Reject absolute paths or any `..` components.
97+
//
98+
// `Path::is_absolute()` is platform-aware: on Windows it requires
99+
// a drive letter or UNC prefix, so a tar entry like `/etc/passwd`
100+
// is NOT considered absolute and would slip through. Explicitly
101+
// check the leading byte for `/` and `\` so the guard rejects
102+
// POSIX-style absolute paths on every platform.
103+
let leading_separator = path_str
104+
.as_bytes()
105+
.first()
106+
.is_some_and(|b| *b == b'/' || *b == b'\\');
97107
if path.is_absolute()
108+
|| leading_separator
98109
|| path
99110
.components()
100111
.any(|c| matches!(c, std::path::Component::ParentDir))
@@ -274,6 +285,18 @@ mod tests {
274285
assert!(matches!(err, ArchiveError::UnsafePath(_)));
275286
}
276287

288+
#[test]
289+
fn test_read_archive_rejects_backslash_absolute_paths() {
290+
// Tar entries with a leading backslash must also be rejected so
291+
// the guard behaves consistently across POSIX and Windows.
292+
let dir = tempfile::tempdir().unwrap();
293+
let archive = dir.path().join("arc.tar.gz");
294+
write_raw_archive(&archive, b"\\Windows\\System32\\evil.dll", b"evil");
295+
296+
let err = read_archive_to_map(&archive).unwrap_err();
297+
assert!(matches!(err, ArchiveError::UnsafePath(_)));
298+
}
299+
277300
#[test]
278301
fn test_read_archive_rejects_parent_traversal() {
279302
let dir = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)