Skip to content

Commit a9064f2

Browse files
MOMO0302-02claude
andcommitted
Skip drive-qualified archive entries instead of failing the restore
Extracting the entry check moved the join to a per-segment form, which made `C:/evil.txt` slip through: it is not posix-absolute and carries no `..`, so it became an ordinary segment and produced a path inside the restore directory that Windows cannot create. Writing it threw an uncaught FileSystemException and aborted the whole restore, where the previous inline code simply skipped the entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 033773a commit a9064f2

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

lib/common/task.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,8 +558,17 @@ String? resolveArchiveEntryPath(String rootPath, String name) {
558558
entryPath.startsWith('../')) {
559559
return null;
560560
}
561+
final segments = posix.split(entryPath);
562+
// A drive-qualified segment such as `C:` is not posix-absolute, so it
563+
// survives the checks above and would be joined as an ordinary segment.
564+
// The resulting path stays inside the root but cannot be created on
565+
// Windows, which would abort the whole restore instead of skipping one
566+
// entry.
567+
if (segments.any((segment) => segment.contains(':'))) {
568+
return null;
569+
}
561570
// Join per segment so the result uses the platform separator.
562-
final outPath = joinAll([rootPath, ...posix.split(entryPath)]);
571+
final outPath = joinAll([rootPath, ...segments]);
563572
if (!isWithin(rootPath, outPath)) {
564573
return null;
565574
}

test/common/task_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ void main() {
4141
expect(resolveArchiveEntryPath(root, '/etc/passwd'), isNull);
4242
});
4343

44+
// Not posix-absolute, so it reaches the join as an ordinary segment and
45+
// yields a path inside the root that Windows cannot create.
46+
test('rejects a drive-qualified entry', () {
47+
expect(resolveArchiveEntryPath(root, 'C:/evil.txt'), isNull);
48+
expect(resolveArchiveEntryPath(root, r'C:\evil.txt'), isNull);
49+
expect(resolveArchiveEntryPath(root, 'profiles/C:/evil.txt'), isNull);
50+
});
51+
52+
test('rejects a UNC entry', () {
53+
expect(resolveArchiveEntryPath(root, r'\\server\share\evil.txt'), isNull);
54+
});
55+
4456
test('rejects backslash traversal', () {
4557
expect(resolveArchiveEntryPath(root, r'..\evil'), isNull);
4658
expect(resolveArchiveEntryPath(root, r'profiles\..\..\evil'), isNull);

0 commit comments

Comments
 (0)