diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
index 630ccdbb75e..35a965a622e 100644
--- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
+++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
@@ -1689,6 +1689,14 @@
Ignoring a native reference with no name in the binding resource package '{0}'.
Shown when a binding resource package's manifest contains a native reference whose 'Name' attribute is missing or empty.
{0} - The path to the binding resource package.
+
+
+ The resource '{0}' in assembly '{1}' would extract to '{2}' which is outside of the target directory '{3}'.
+ Shown when an embedded resource would be extracted to a path outside the intended output directory.
+{0} - The resource name.
+{1} - The assembly path.
+{2} - The computed extraction path.
+{3} - The intended output directory.
The PrepareAssemblies task failed without reporting a specific error. Please rebuild with increased verbosity for more details.
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs
index 564ef4c6807..7f95ade477a 100644
--- a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs
@@ -12,6 +12,7 @@
using Microsoft.Build.Utilities;
using Xamarin.Localization.MSBuild;
+using Xamarin.Utils;
#nullable enable
@@ -350,7 +351,12 @@ List ExtractContentAssembly (string assembly, string in
continue;
}
- var path = Path.Combine (intermediatePath, itemType, rpath);
+ var extractionDirectory = Path.Combine (intermediatePath, itemType);
+ var path = Path.Combine (extractionDirectory, rpath);
+ if (!PathUtils.IsPathContained (extractionDirectory, path)) {
+ Log.LogError (MSBStrings.E7183 /* The resource '{0}' in assembly '{1}' would extract to '{2}' which is outside of the target directory '{3}'. */, resourceName, assembly, path, extractionDirectory);
+ continue;
+ }
var file = new FileInfo (path);
var item = new TaskItem (path);
diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs
new file mode 100644
index 00000000000..5524b1c4cca
--- /dev/null
+++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+
+using Microsoft.Build.Utilities;
+
+using Mono.Cecil;
+
+using NUnit.Framework;
+
+using Xamarin.Tests;
+
+namespace Xamarin.MacDev.Tasks {
+ [TestFixture]
+ public class UnpackLibraryResourcesTests : TestBase {
+
+ static string CreateAssemblyWithResource (string directory, string assemblyName, string resourceName, byte [] content)
+ {
+ var asmPath = Path.Combine (directory, assemblyName + ".dll");
+ var assemblyDef = AssemblyDefinition.CreateAssembly (
+ new AssemblyNameDefinition (assemblyName, new Version (1, 0, 0, 0)),
+ assemblyName,
+ ModuleKind.Dll);
+
+ var resource = new EmbeddedResource (resourceName, ManifestResourceAttributes.Public, content);
+ assemblyDef.MainModule.Resources.Add (resource);
+ assemblyDef.Write (asmPath);
+
+ return asmPath;
+ }
+
+ [Test]
+ public void PathTraversal_IsRejected ()
+ {
+ var tmpdir = Cache.CreateTemporaryDirectory ();
+ // Resource name "__monotouch_content_.._sEvil.txt" unmangles to "../Evil.txt" (path traversal)
+ var assemblyPath = CreateAssemblyWithResource (tmpdir, "TestTraversal", "__monotouch_content_.._sEvil.txt", new byte [] { 0x41 });
+
+ var task = CreateTask ();
+ task.Prefix = "monotouch";
+ task.IntermediateOutputPath = Path.Combine (tmpdir, "intermediate");
+ task.ReferencedLibraries = new [] { new TaskItem (assemblyPath) };
+ task.TargetFrameworkDirectory = Array.Empty ();
+
+ ExecuteTask (task, expectedErrorCount: 1);
+
+ Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("would extract to"));
+ Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("outside"));
+
+ // Verify the file was NOT written outside the target directory
+ var escapedPath = Path.Combine (tmpdir, "intermediate", "unpack", "TestTraversal", "Evil.txt");
+ Assert.That (File.Exists (escapedPath), Is.False, "File should not have been extracted outside target directory");
+ }
+
+ [Test]
+ public void ValidResource_IsExtracted ()
+ {
+ var tmpdir = Cache.CreateTemporaryDirectory ();
+ // Resource name "__monotouch_content_sub_sfile.txt" unmangles to "sub/file.txt" (valid path)
+ var assemblyPath = CreateAssemblyWithResource (tmpdir, "TestValid", "__monotouch_content_sub_sfile.txt", new byte [] { 0x41 });
+
+ var task = CreateTask ();
+ task.Prefix = "monotouch";
+ task.IntermediateOutputPath = Path.Combine (tmpdir, "intermediate");
+ task.ReferencedLibraries = new [] { new TaskItem (assemblyPath) };
+ task.TargetFrameworkDirectory = Array.Empty ();
+
+ ExecuteTask (task, expectedErrorCount: 0);
+
+ var extractedPath = Path.Combine (tmpdir, "intermediate", "unpack", "TestValid", "content", "sub", "file.txt");
+ Assert.That (File.Exists (extractedPath), Is.True, $"File should have been extracted to {extractedPath}");
+ }
+ }
+}
diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj
index bbd8daba35b..d174238864c 100644
--- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj
+++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj
@@ -45,6 +45,7 @@
+