mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
5.8 KiB
150 lines
5.8 KiB
// Copyright (c) 2026 Siegfried Pammer |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System.IO; |
|
using System.Threading.Tasks; |
|
|
|
using ICSharpCode.ILSpyX.FileLoaders; |
|
|
|
using K4os.Compression.LZ4; |
|
|
|
using NUnit.Framework; |
|
|
|
namespace ICSharpCode.Decompiler.Tests |
|
{ |
|
[TestFixture] |
|
public class XamarinCompressedFileLoaderTests |
|
{ |
|
// Magic used for the Xamarin compressed module header ('XALZ', little-endian). |
|
const uint CompressedDataMagic = 0x5A4C4158; |
|
|
|
// Builds an XALZ blob: 4-byte magic, 4-byte descriptor index, 4-byte declared |
|
// uncompressed length, followed by the raw payload bytes. |
|
static MemoryStream BuildBlob(uint magic, uint declaredUncompressedLength, byte[] payload) |
|
{ |
|
var ms = new MemoryStream(); |
|
using (var writer = new BinaryWriter(ms, System.Text.Encoding.UTF8, leaveOpen: true)) |
|
{ |
|
writer.Write(magic); |
|
writer.Write((uint)0); // descriptor table index, unused by the loader |
|
writer.Write(declaredUncompressedLength); |
|
writer.Write(payload); |
|
} |
|
ms.Position = 0; |
|
return ms; |
|
} |
|
|
|
static Task<LoadResult?> Load(MemoryStream blob) |
|
{ |
|
return new XamarinCompressedFileLoader().Load("test.dll", blob, new FileLoadContext(false, null)); |
|
} |
|
|
|
[Test] |
|
public void NonXalzMagic_ReturnsNull() |
|
{ |
|
// A stream that does not start with the XALZ magic is not ours; pass it through. |
|
using var blob = BuildBlob(0x12345678, 16, new byte[16]); |
|
|
|
Assert.That(Load(blob).GetAwaiter().GetResult(), Is.Null); |
|
} |
|
|
|
[Test] |
|
public void TooShortForMagic_ReturnsNull() |
|
{ |
|
// A stream shorter than the 4-byte magic cannot be an XALZ module; pass it through |
|
// rather than letting the magic read throw EndOfStreamException. |
|
using var blob = new MemoryStream(new byte[] { 0x58, 0x41 }); |
|
|
|
Assert.That(Load(blob).GetAwaiter().GetResult(), Is.Null); |
|
} |
|
|
|
[Test] |
|
public void TruncatedHeader_IsRejected() |
|
{ |
|
// The magic is present but the stream ends before the full 12-byte header. This is a |
|
// corrupt module and must fail as a catchable InvalidDataException, not an |
|
// EndOfStreamException from a partial header read. |
|
var ms = new MemoryStream(); |
|
using (var writer = new BinaryWriter(ms, System.Text.Encoding.UTF8, leaveOpen: true)) |
|
{ |
|
writer.Write(CompressedDataMagic); |
|
writer.Write((uint)0); // only 8 of the required 12 header bytes |
|
} |
|
ms.Position = 0; |
|
|
|
Assert.ThrowsAsync<InvalidDataException>(() => Load(ms)); |
|
ms.Dispose(); |
|
} |
|
|
|
[Test] |
|
public void NegativeDeclaredLength_DoesNotThrowArgumentOutOfRange() |
|
{ |
|
// A declared length with the high bit set would become negative when cast to int and |
|
// make ArrayPool.Rent throw ArgumentOutOfRangeException. It must be rejected as |
|
// malformed input (a catchable InvalidDataException) before any allocation. |
|
using var blob = BuildBlob(CompressedDataMagic, 0xFFFFFFFF, new byte[] { 1, 2, 3, 4 }); |
|
|
|
Assert.ThrowsAsync<InvalidDataException>(() => Load(blob)); |
|
} |
|
|
|
[Test] |
|
public void ImplausiblyHugeDeclaredLength_IsRejectedWithoutAllocating() |
|
{ |
|
// A tiny payload declaring a ~2 GB decompressed size is a decompression-bomb header: |
|
// no LZ4 block that small can expand that far. Reject it instead of renting ~2 GB. |
|
using var blob = BuildBlob(CompressedDataMagic, 0x7FFFFFFF, new byte[] { 1, 2, 3, 4 }); |
|
|
|
Assert.ThrowsAsync<InvalidDataException>(() => Load(blob)); |
|
} |
|
|
|
[Test] |
|
public void CorruptPayload_IsRejected() |
|
{ |
|
// Garbage that is not a valid LZ4 block, with an otherwise plausible declared length, |
|
// must surface as a catchable InvalidDataException rather than producing a buffer of |
|
// stale/partial bytes. |
|
byte[] garbage = new byte[64]; |
|
for (int i = 0; i < garbage.Length; i++) |
|
garbage[i] = (byte)(i * 7 + 1); |
|
using var blob = BuildBlob(CompressedDataMagic, 4096, garbage); |
|
|
|
Assert.ThrowsAsync<InvalidDataException>(() => Load(blob)); |
|
} |
|
|
|
[Test] |
|
public void ValidXalz_LoadsDecompressedAssembly() |
|
{ |
|
// A genuine XALZ wrapper around a real, LZ4-compressed assembly must still load. This |
|
// also exercises the decoded-length slice: ArrayPool.Rent may hand back a buffer larger |
|
// than the decompressed data, and the PE parser must see only the real bytes. |
|
byte[] original = File.ReadAllBytes(typeof(XamarinCompressedFileLoader).Assembly.Location); |
|
byte[] compressed = new byte[LZ4Codec.MaximumOutputSize(original.Length)]; |
|
int compressedLength = LZ4Codec.Encode(original, 0, original.Length, compressed, 0, compressed.Length); |
|
byte[] payload = new byte[compressedLength]; |
|
System.Array.Copy(compressed, payload, compressedLength); |
|
|
|
using var blob = BuildBlob(CompressedDataMagic, (uint)original.Length, payload); |
|
|
|
var result = Load(blob).GetAwaiter().GetResult(); |
|
|
|
Assert.That(result, Is.Not.Null); |
|
Assert.That(result!.IsSuccess, Is.True); |
|
Assert.That(result.MetadataFile, Is.Not.Null); |
|
} |
|
} |
|
}
|
|
|