From 11e46fca72a7ac876bb8b2f7e0fd03e27c4c1d73 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 2 Jul 2026 14:26:56 +0200 Subject: [PATCH 1/6] [msbuild] Validate extracted resource paths in UnpackLibraryResources Ensure that the resolved path for extracted resources stays within the intended output directory. If an embedded resource name contains path traversal sequences (e.g. '..'), the task now logs an error and skips that resource. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx | 6 ++++++ .../Tasks/UnpackLibraryResources.cs | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index 630ccdbb75e3..d148f4c0f41f 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1689,6 +1689,12 @@ 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 a path outside of the target directory. + Shown when an embedded resource would be extracted to a path outside the intended output directory. +{0} - The resource name. +{1} - The assembly path. 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 564ef4c6807d..97fbf8e7d272 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,14 @@ 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); + var fullPath = Path.GetFullPath (path); + var expectedPrefix = Path.GetFullPath (extractionDirectory).EnsureTrailingSlash (); + if (!fullPath.StartsWith (expectedPrefix, StringComparison.Ordinal)) { + Log.LogError (MSBStrings.E7183 /* The resource '{0}' in assembly '{1}' would extract to a path outside of the target directory. */, resourceName, assembly); + continue; + } var file = new FileInfo (path); var item = new TaskItem (path); From 0e38e90c470383d0393c442fd804b5a5ededbaad Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 2 Jul 2026 19:02:31 +0200 Subject: [PATCH 2/6] [msbuild] Use PathUtils.IsPathContained for path validation Switch from manual GetFullPath+StartsWith check to the existing PathUtils.IsPathContained helper which also handles symlinks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs index 97fbf8e7d272..4ee21838f7cd 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs @@ -353,9 +353,7 @@ List ExtractContentAssembly (string assembly, string in var extractionDirectory = Path.Combine (intermediatePath, itemType); var path = Path.Combine (extractionDirectory, rpath); - var fullPath = Path.GetFullPath (path); - var expectedPrefix = Path.GetFullPath (extractionDirectory).EnsureTrailingSlash (); - if (!fullPath.StartsWith (expectedPrefix, StringComparison.Ordinal)) { + if (!PathUtils.IsPathContained (extractionDirectory, path)) { Log.LogError (MSBStrings.E7183 /* The resource '{0}' in assembly '{1}' would extract to a path outside of the target directory. */, resourceName, assembly); continue; } From ad2a84c9f40176633d25c1614f6e7e4a9d9b52b6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 2 Jul 2026 20:14:35 +0200 Subject: [PATCH 3/6] [tests] Add UnpackLibraryResources path traversal tests Add tests verifying that: - Resources with path traversal sequences are rejected with E7183. - Valid resources are still extracted correctly. Also update E7183 to include the computed path and target directory in the error message for easier diagnosis. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../MSBStrings.resx | 6 +- .../Tasks/UnpackLibraryResources.cs | 2 +- .../TaskTests/UnpackLibraryResourcesTests.cs | 120 ++++++++++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index d148f4c0f41f..35a965a622e3 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1691,10 +1691,12 @@ {0} - The path to the binding resource package. - The resource '{0}' in assembly '{1}' would extract to a path outside of the target directory. + 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. +{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 4ee21838f7cd..7f95ade477a4 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs @@ -354,7 +354,7 @@ List ExtractContentAssembly (string assembly, string in 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 a path outside of the target directory. */, resourceName, assembly); + 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); 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 000000000000..a96a09535def --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; + +using Microsoft.Build.Utilities; + +using NUnit.Framework; + +using Xamarin.Tests; + +namespace Xamarin.MacDev.Tasks { + [TestFixture] + public class UnpackLibraryResourcesTests : TestBase { + + static string CreateAssemblyWithResource (string directory, string resourceName, byte [] content) + { + var asmPath = Path.Combine (directory, "TestLib.dll"); + + var metadataBuilder = new MetadataBuilder (); + + metadataBuilder.AddModule ( + 0, + metadataBuilder.GetOrAddString ("TestLib.dll"), + metadataBuilder.GetOrAddGuid (Guid.NewGuid ()), + default, + default); + + metadataBuilder.AddAssembly ( + metadataBuilder.GetOrAddString ("TestLib"), + new Version (1, 0, 0, 0), + default, + default, + default, + AssemblyHashAlgorithm.None); + + // Add the embedded resource + var resourceBlob = metadataBuilder.GetOrAddBlob (content); + metadataBuilder.AddManifestResource ( + ManifestResourceAttributes.Public, + metadataBuilder.GetOrAddString (resourceName), + default, + 0); + + var metadataRootBuilder = new MetadataRootBuilder (metadataBuilder); + + // Build the PE with the resource data + var peHeaderBuilder = new PEHeaderBuilder (imageCharacteristics: Characteristics.Dll); + var resourceBlobBuilder = new BlobBuilder (); + // Resource format: 4-byte length prefix followed by the data + resourceBlobBuilder.WriteInt32 (content.Length); + resourceBlobBuilder.WriteBytes (content); + + var peBuilder = new ManagedPEBuilder ( + peHeaderBuilder, + metadataRootBuilder, + ilStream: new BlobBuilder (), + managedResources: resourceBlobBuilder); + + var blobBuilder = new BlobBuilder (); + peBuilder.Serialize (blobBuilder); + + using var fs = new FileStream (asmPath, FileMode.Create, FileAccess.Write); + blobBuilder.WriteContentTo (fs); + + return asmPath; + } + + [Test] + public void PathTraversal_IsRejected () + { + var tmpdir = Cache.CreateTemporaryDirectory (); + var prefix = "monotouch"; + // Mangled resource name: ".._sEvil.txt" unmangles to "../Evil.txt" (path traversal) + var resourceName = $"__{prefix}_content_.._sEvil.txt"; + var assemblyPath = CreateAssemblyWithResource (tmpdir, resourceName, new byte [] { 0x41 }); + + var task = CreateTask (); + task.Prefix = prefix; + 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", "TestLib", "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 (); + var prefix = "monotouch"; + // Mangled resource name: "sub_sfile.txt" unmangles to "sub/file.txt" (valid path) + var resourceName = $"__{prefix}_content_sub_sfile.txt"; + var assemblyPath = CreateAssemblyWithResource (tmpdir, resourceName, new byte [] { 0x41 }); + + var task = CreateTask (); + task.Prefix = prefix; + 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", "TestLib", "content", "sub", "file.txt"); + Assert.That (File.Exists (extractedPath), Is.True, $"File should have been extracted to {extractedPath}"); + } + } +} From ef3d0cb2f2777316e152f855b25330afb7997a39 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 2 Jul 2026 20:33:33 +0200 Subject: [PATCH 4/6] [tests] Fix CS0433 build error in UnpackLibraryResourcesTests Use pre-compiled test assemblies instead of System.Reflection.Metadata APIs to avoid type conflicts between the framework and package versions of System.Reflection.Metadata referenced by the test project. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TaskTests/UnpackLibraryResourcesTests.cs | 81 ++++--------------- .../Xamarin.MacDev.Tasks.Tests.csproj | 8 ++ 2 files changed, 23 insertions(+), 66 deletions(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs index a96a09535def..9054792740f8 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs @@ -3,10 +3,6 @@ using System; using System.IO; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; using Microsoft.Build.Utilities; @@ -18,70 +14,26 @@ namespace Xamarin.MacDev.Tasks { [TestFixture] public class UnpackLibraryResourcesTests : TestBase { - static string CreateAssemblyWithResource (string directory, string resourceName, byte [] content) + // Test assemblies were compiled with: + // csc -target:library -out:UnpackLibraryResources-Traversal.dll -resource:traversal.txt,__monotouch_content_.._sEvil.txt empty.cs + // csc -target:library -out:UnpackLibraryResources-Valid.dll -resource:valid.txt,__monotouch_content_sub_sfile.txt empty.cs + + static string GetTestDataPath (string filename) { - var asmPath = Path.Combine (directory, "TestLib.dll"); - - var metadataBuilder = new MetadataBuilder (); - - metadataBuilder.AddModule ( - 0, - metadataBuilder.GetOrAddString ("TestLib.dll"), - metadataBuilder.GetOrAddGuid (Guid.NewGuid ()), - default, - default); - - metadataBuilder.AddAssembly ( - metadataBuilder.GetOrAddString ("TestLib"), - new Version (1, 0, 0, 0), - default, - default, - default, - AssemblyHashAlgorithm.None); - - // Add the embedded resource - var resourceBlob = metadataBuilder.GetOrAddBlob (content); - metadataBuilder.AddManifestResource ( - ManifestResourceAttributes.Public, - metadataBuilder.GetOrAddString (resourceName), - default, - 0); - - var metadataRootBuilder = new MetadataRootBuilder (metadataBuilder); - - // Build the PE with the resource data - var peHeaderBuilder = new PEHeaderBuilder (imageCharacteristics: Characteristics.Dll); - var resourceBlobBuilder = new BlobBuilder (); - // Resource format: 4-byte length prefix followed by the data - resourceBlobBuilder.WriteInt32 (content.Length); - resourceBlobBuilder.WriteBytes (content); - - var peBuilder = new ManagedPEBuilder ( - peHeaderBuilder, - metadataRootBuilder, - ilStream: new BlobBuilder (), - managedResources: resourceBlobBuilder); - - var blobBuilder = new BlobBuilder (); - peBuilder.Serialize (blobBuilder); - - using var fs = new FileStream (asmPath, FileMode.Create, FileAccess.Write); - blobBuilder.WriteContentTo (fs); - - return asmPath; + var path = Path.Combine (TestContext.CurrentContext.TestDirectory, "TestData", filename); + if (!File.Exists (path)) + Assert.Ignore ($"Test data file not found: {path}"); + return path; } [Test] public void PathTraversal_IsRejected () { var tmpdir = Cache.CreateTemporaryDirectory (); - var prefix = "monotouch"; - // Mangled resource name: ".._sEvil.txt" unmangles to "../Evil.txt" (path traversal) - var resourceName = $"__{prefix}_content_.._sEvil.txt"; - var assemblyPath = CreateAssemblyWithResource (tmpdir, resourceName, new byte [] { 0x41 }); + var assemblyPath = GetTestDataPath ("UnpackLibraryResources-Traversal.dll"); var task = CreateTask (); - task.Prefix = prefix; + task.Prefix = "monotouch"; task.IntermediateOutputPath = Path.Combine (tmpdir, "intermediate"); task.ReferencedLibraries = new [] { new TaskItem (assemblyPath) }; task.TargetFrameworkDirectory = Array.Empty (); @@ -92,7 +44,7 @@ public void PathTraversal_IsRejected () 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", "TestLib", "Evil.txt"); + var escapedPath = Path.Combine (tmpdir, "intermediate", "unpack", "UnpackLibraryResources-Traversal", "Evil.txt"); Assert.That (File.Exists (escapedPath), Is.False, "File should not have been extracted outside target directory"); } @@ -100,20 +52,17 @@ public void PathTraversal_IsRejected () public void ValidResource_IsExtracted () { var tmpdir = Cache.CreateTemporaryDirectory (); - var prefix = "monotouch"; - // Mangled resource name: "sub_sfile.txt" unmangles to "sub/file.txt" (valid path) - var resourceName = $"__{prefix}_content_sub_sfile.txt"; - var assemblyPath = CreateAssemblyWithResource (tmpdir, resourceName, new byte [] { 0x41 }); + var assemblyPath = GetTestDataPath ("UnpackLibraryResources-Valid.dll"); var task = CreateTask (); - task.Prefix = prefix; + 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", "TestLib", "content", "sub", "file.txt"); + var extractedPath = Path.Combine (tmpdir, "intermediate", "unpack", "UnpackLibraryResources-Valid", "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 bbd8daba35b8..07734b8fbd4a 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 @@ -131,4 +131,12 @@ PreserveNewest + + + PreserveNewest + + + PreserveNewest + + From 609bcf57f9bfef044f98797ae3ae14650e803c3b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 2 Jul 2026 20:34:02 +0200 Subject: [PATCH 5/6] [tests] Add pre-compiled test assemblies for UnpackLibraryResources tests These were compiled with: csc -target:library -out:Traversal.dll -resource:r.txt,__monotouch_content_.._sEvil.txt empty.cs csc -target:library -out:Valid.dll -resource:r.txt,__monotouch_content_sub_sfile.txt empty.cs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../UnpackLibraryResources-Traversal.dll | Bin 0 -> 2560 bytes .../TestData/UnpackLibraryResources-Valid.dll | Bin 0 -> 2560 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Traversal.dll create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Valid.dll diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Traversal.dll b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Traversal.dll new file mode 100644 index 0000000000000000000000000000000000000000..fcd4146903aea4a2268c4c6fb4efa65018baee8b GIT binary patch literal 2560 zcmeHIziSg=7=ErzORa5j5D^@_v=toWh}glQXiU>;CAG!0MO@NLa;7JjyK;9ax=02E zCvotP=uo7CLlMMH1nDXT9UR;{3J%ui{q8PFOR;n)IP|@I-}gT6@AvybW*)vqH-Hq! z?k=#&RVb_Ge@{)Shc3P!!n;#j=}oP$m7ZU=1AWE!YQ9<5E2isup;F7IwI(EhO8Uew2TBH$u5?+tNz9l)UNL-rm{Ji7Ln6`_0Q1N`rQgw-T z_Yz|q<=Em(nsPc1G-E+au93?eAQuC&+O*o#+Uwq;e-TMm6CxjtvJEW4d?*Pw7Y1O`50Hq%@%3L`XZ2 zD58zjIvdhhQ%EbD^4bBBUMiam`;NXblw}Wag%xQn`#iG;sHiwAd-CYC&F~SnZ5L_l z)4E+t1_o`C0gPja_R>wLY+cswaKB8{iTNH?SF3Rx(HVSK4nN?ss;fXBILS^5Iy~2y>T8ZMJmzn(|BxrLx4U*<(jRbL`P^dv0WIs? eO^4O#hx*a=gpO+m^yoih`|5EeIEni|@4#Q_)xw+r literal 0 HcmV?d00001 diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Valid.dll b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Valid.dll new file mode 100644 index 0000000000000000000000000000000000000000..45f475ba9494e46d2ab71f799e753cf15caab13f GIT binary patch literal 2560 zcmeHIJ8KkC6#gdJNQ@>}Nf5zV6VW0pD=uQO=w!2rM)D$?1hLudW3m~Uoe49u5rxZ`S;1-Hj)G0_D zsT;;)ag|?dZHS^GJ!IZlVGQ6|BTmF7EdwW;!T}f^=xuZFrF^SRP6Xz9K>3Tti8h6K zSAD9N6Q1vtXja$^GSXPZul)RlnqVrm1j=b>{+e9OSrm z71LDkAW-i#geRKZLoa@EckUQGQyxl0>8=#nCFGFjJdSyClYD72lqWe)gs%?oV&8v- zCl*D9>y3seccLw=(#oCCn6&_pc`Vp4nbW}vTxB0tH)(;^0<}Kv1qAeSDTTBVd#*{2 z9Q7V84k~+OT30?vL|+O;^1uiYX-)qutNSRaJZl|wbowUw2wT2O^!4c7!jeRSKG6VD z=%c@M6DVJY*lq4t0aePpKT5XvmJ%E`Kj4zWGevtvS;yGv8XhP_6|6E7)YVF>+_Me6 zbS=4~9Te&BHBzdNImWuITVuZgPoT4#Rv*$2a81RGvR|K`RqmcmRQkSg3_YU5+5tWM Q&-i@#unHd4`JZ;+H(~a Date: Fri, 3 Jul 2026 12:28:46 +0200 Subject: [PATCH 6/6] [tests] Use Mono.Cecil to create test assemblies at runtime Replace pre-compiled binary test DLLs with runtime assembly creation using Mono.Cecil. This avoids checking in binary files and makes the test self-contained. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TaskTests/UnpackLibraryResourcesTests.cs | 33 +++++++++++------- .../UnpackLibraryResources-Traversal.dll | Bin 2560 -> 0 bytes .../TestData/UnpackLibraryResources-Valid.dll | Bin 2560 -> 0 bytes .../Xamarin.MacDev.Tasks.Tests.csproj | 9 +---- 4 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Traversal.dll delete mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Valid.dll diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs index 9054792740f8..5524b1c4cca6 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/UnpackLibraryResourcesTests.cs @@ -6,6 +6,8 @@ using Microsoft.Build.Utilities; +using Mono.Cecil; + using NUnit.Framework; using Xamarin.Tests; @@ -14,23 +16,27 @@ namespace Xamarin.MacDev.Tasks { [TestFixture] public class UnpackLibraryResourcesTests : TestBase { - // Test assemblies were compiled with: - // csc -target:library -out:UnpackLibraryResources-Traversal.dll -resource:traversal.txt,__monotouch_content_.._sEvil.txt empty.cs - // csc -target:library -out:UnpackLibraryResources-Valid.dll -resource:valid.txt,__monotouch_content_sub_sfile.txt empty.cs - - static string GetTestDataPath (string filename) + static string CreateAssemblyWithResource (string directory, string assemblyName, string resourceName, byte [] content) { - var path = Path.Combine (TestContext.CurrentContext.TestDirectory, "TestData", filename); - if (!File.Exists (path)) - Assert.Ignore ($"Test data file not found: {path}"); - return path; + 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 (); - var assemblyPath = GetTestDataPath ("UnpackLibraryResources-Traversal.dll"); + // 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"; @@ -44,7 +50,7 @@ public void PathTraversal_IsRejected () 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", "UnpackLibraryResources-Traversal", "Evil.txt"); + 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"); } @@ -52,7 +58,8 @@ public void PathTraversal_IsRejected () public void ValidResource_IsExtracted () { var tmpdir = Cache.CreateTemporaryDirectory (); - var assemblyPath = GetTestDataPath ("UnpackLibraryResources-Valid.dll"); + // 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"; @@ -62,7 +69,7 @@ public void ValidResource_IsExtracted () ExecuteTask (task, expectedErrorCount: 0); - var extractedPath = Path.Combine (tmpdir, "intermediate", "unpack", "UnpackLibraryResources-Valid", "content", "sub", "file.txt"); + 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/TestData/UnpackLibraryResources-Traversal.dll b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Traversal.dll deleted file mode 100644 index fcd4146903aea4a2268c4c6fb4efa65018baee8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2560 zcmeHIziSg=7=ErzORa5j5D^@_v=toWh}glQXiU>;CAG!0MO@NLa;7JjyK;9ax=02E zCvotP=uo7CLlMMH1nDXT9UR;{3J%ui{q8PFOR;n)IP|@I-}gT6@AvybW*)vqH-Hq! z?k=#&RVb_Ge@{)Shc3P!!n;#j=}oP$m7ZU=1AWE!YQ9<5E2isup;F7IwI(EhO8Uew2TBH$u5?+tNz9l)UNL-rm{Ji7Ln6`_0Q1N`rQgw-T z_Yz|q<=Em(nsPc1G-E+au93?eAQuC&+O*o#+Uwq;e-TMm6CxjtvJEW4d?*Pw7Y1O`50Hq%@%3L`XZ2 zD58zjIvdhhQ%EbD^4bBBUMiam`;NXblw}Wag%xQn`#iG;sHiwAd-CYC&F~SnZ5L_l z)4E+t1_o`C0gPja_R>wLY+cswaKB8{iTNH?SF3Rx(HVSK4nN?ss;fXBILS^5Iy~2y>T8ZMJmzn(|BxrLx4U*<(jRbL`P^dv0WIs? eO^4O#hx*a=gpO+m^yoih`|5EeIEni|@4#Q_)xw+r diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Valid.dll b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestData/UnpackLibraryResources-Valid.dll deleted file mode 100644 index 45f475ba9494e46d2ab71f799e753cf15caab13f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2560 zcmeHIJ8KkC6#gdJNQ@>}Nf5zV6VW0pD=uQO=w!2rM)D$?1hLudW3m~Uoe49u5rxZ`S;1-Hj)G0_D zsT;;)ag|?dZHS^GJ!IZlVGQ6|BTmF7EdwW;!T}f^=xuZFrF^SRP6Xz9K>3Tti8h6K zSAD9N6Q1vtXja$^GSXPZul)RlnqVrm1j=b>{+e9OSrm z71LDkAW-i#geRKZLoa@EckUQGQyxl0>8=#nCFGFjJdSyClYD72lqWe)gs%?oV&8v- zCl*D9>y3seccLw=(#oCCn6&_pc`Vp4nbW}vTxB0tH)(;^0<}Kv1qAeSDTTBVd#*{2 z9Q7V84k~+OT30?vL|+O;^1uiYX-)qutNSRaJZl|wbowUw2wT2O^!4c7!jeRSKG6VD z=%c@M6DVJY*lq4t0aePpKT5XvmJ%E`Kj4zWGevtvS;yGv8XhP_6|6E7)YVF>+_Me6 zbS=4~9Te&BHBzdNImWuITVuZgPoT4#Rv*$2a81RGvR|K`RqmcmRQkSg3_YU5+5tWM Q&-i@#unHd4`JZ;+H(~a + @@ -131,12 +132,4 @@ PreserveNewest - - - PreserveNewest - - - PreserveNewest - -