Browse Source

Handle truncated XALZ input via EndOfStreamException, not length checks

Pre-checking stream.Length before reading is racy (the stream can shrink
between check and read) and not every stream knows its length ahead of
time. Instead, catch EndOfStreamException at each read: a stream too
short for the magic is passed through as not-XALZ, and a truncated
header or payload after a confirmed magic is rejected as
InvalidDataException. stream.Length remains only a sizing hint for the
payload buffer.

Assisted-by: Claude:claude-fable-5:Claude Code
pull/3864/merge
Siegfried Pammer 2 days ago committed by Siegfried Pammer
parent
commit
4d42b38af1
  1. 42
      ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs

42
ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs

@ -36,19 +36,31 @@ namespace ICSharpCode.ILSpyX.FileLoaders @@ -36,19 +36,31 @@ namespace ICSharpCode.ILSpyX.FileLoaders
const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian)
using var fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
// Read compressed file header
if (stream.Length < sizeof(uint))
uint magic;
try
{
magic = fileReader.ReadUInt32();
}
catch (EndOfStreamException)
{
// Too short to contain the magic, so this cannot be an XALZ module; pass it through.
return null;
var magic = fileReader.ReadUInt32();
}
if (magic != CompressedDataMagic)
return null;
// The magic identifies this as an XALZ module, so it must carry the full 12-byte
// header: magic, descriptor table index, and uncompressed length. A shorter stream is
// a truncated/corrupt module; fail consistently instead of letting a later read throw
// EndOfStreamException.
if (stream.Length < 3 * sizeof(uint))
throw new InvalidDataException("Invalid Xamarin compressed module: truncated header.");
uint declaredUncompressedLength;
try
{
_ = fileReader.ReadUInt32(); // skip index into descriptor table, unused
uint declaredUncompressedLength = fileReader.ReadUInt32();
declaredUncompressedLength = fileReader.ReadUInt32();
}
catch (EndOfStreamException ex)
{
// The magic identifies this as an XALZ module, so it must carry the full 12-byte
// header: magic, descriptor table index, and uncompressed length. A shorter stream
// is a truncated/corrupt module; fail consistently as InvalidDataException.
throw new InvalidDataException("Invalid Xamarin compressed module: truncated header.", ex);
}
// The compressed payload is whatever follows the 12-byte header, not the whole file.
long compressedLength = stream.Length - stream.Position;
// The declared uncompressed length is attacker-controlled. Reject implausible values
@ -71,8 +83,18 @@ namespace ICSharpCode.ILSpyX.FileLoaders @@ -71,8 +83,18 @@ namespace ICSharpCode.ILSpyX.FileLoaders
var dst = pool.Rent(uncompressedLength);
try
{
// fileReader stream position is now at compressed module data
// fileReader stream position is now at compressed module data.
// stream.Length above is only a sizing hint; if the stream ends early (e.g. the
// file was truncated after the length was queried), treat that as corrupt input
// rather than surfacing EndOfStreamException.
try
{
await stream.ReadExactlyAsync(src, 0, compressed).ConfigureAwait(false);
}
catch (EndOfStreamException ex)
{
throw new InvalidDataException("Invalid Xamarin compressed module: truncated payload.", ex);
}
// Decompress; Decode returns the number of bytes written, or negative on failure.
// The header declares the exact decompressed size, so anything other than an exact
// match (a negative error code or a short, truncated decode) means the payload is

Loading…
Cancel
Save