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 @@ +