From c58087db7efd8a81ce9be842be7ebb16d6a337e4 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 16 Jun 2026 10:20:27 +0200 Subject: [PATCH] Fixed tests to check for errors, instead of crashing the restore process on path traversal checks --- .../Library/Main/Operation/RestoreHandler.cs | 16 ++++++++++++-- .../UnitTest/RestorePathTraversalTests.cs | 21 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index 37c91f054..2ff6f5c75 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -1173,7 +1173,19 @@ namespace Duplicati.Library.Main.Operation var targetfileid = restorelist.TargetFileID; var targetfilehash = restorelist.TargetHash; var targetfilelength = restorelist.Length; - if (await restoreDestination.FileExists(targetpath, result.TaskControl.ProgressToken).ConfigureAwait(false)) + var fileExists = false; + try + { + fileExists = await restoreDestination.FileExists(targetpath, result.TaskControl.ProgressToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FileExistsFailed", ex, "Failed to check if file exists: \"{0}\", message: {1}", targetpath, ex.Message); + if (options.UnittestMode) + throw; + } + + if (fileExists) { try { @@ -1208,7 +1220,7 @@ namespace Duplicati.Library.Main.Operation { // a file hash for verification will only be necessary if the file has exactly // the wanted size so we have a chance to already mark the file as data-verified. - bool calcFileHash = (currentfilelength == targetfilelength); + var calcFileHash = currentfilelength == targetfilelength; if (calcFileHash) filehasher.Initialize(); using (var file = await restoreDestination.OpenRead(targetpath, result.TaskControl.ProgressToken).ConfigureAwait(false)) diff --git a/Duplicati/UnitTest/RestorePathTraversalTests.cs b/Duplicati/UnitTest/RestorePathTraversalTests.cs index e63c9e49c..0ae32bcc0 100644 --- a/Duplicati/UnitTest/RestorePathTraversalTests.cs +++ b/Duplicati/UnitTest/RestorePathTraversalTests.cs @@ -16,7 +16,7 @@ namespace Duplicati.UnitTest { [Test] [Category("RestoreHandler")] - public async Task RestoreToSymlinkTargetAsync() + public async Task RestoreToSymlinkTargetAsync([Values(true, false)] bool legacyRestore) { if (OperatingSystem.IsWindows()) Assert.Ignore("Symlink tests are not supported on Windows"); @@ -53,12 +53,25 @@ namespace Duplicati.UnitTest var restoreOptions = new Dictionary(this.TestOptions); restoreOptions["restore-path"] = restoreTarget; restoreOptions["overwrite"] = "true"; + restoreOptions["restore-legacy"] = legacyRestore.ToString(); + restoreOptions["unittest-mode"] = "false"; using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - // We expect a UserInformationException due to path traversal detection - var ex = NUnit.Framework.Assert.ThrowsAsync(() => c.RestoreAsync(null)); - NUnit.Framework.Assert.That(ex.Message.Contains("Path traversal detected"), "Expected path traversal error message"); + // Verify that path traversal is detected + var restoreResults = await c.RestoreAsync(null); + var foundError = restoreResults.Errors.Any(e => e.Contains("Path traversal detected")); + if (!foundError) + { + Console.WriteLine("Errors found:"); + foreach (var e in restoreResults.Errors) + Console.WriteLine(e); + + Console.WriteLine("Warnings found:"); + foreach (var e in restoreResults.Warnings) + Console.WriteLine(e); + } + Assert.IsTrue(foundError, "Expected path traversal error message"); } // 5. Verify vulnerability