Skip to content

IndexOutOfRangeException in ReadCompressedTextChunk and ReadInternationalTextChunk with truncated chunks #3079

@pawlos

Description

@pawlos

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of ImageSharp
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

ImageSharp version

3.1.12

Other ImageSharp packages and versions

N/A — only SixLabors.ImageSharp

Environment (Operating system, version and so on)

Linux (WSL2, Ubuntu 22.04) and Windows 10

.NET Framework version

.NET 10.0 (SDK 10.0.103 / Runtime 10.0.3)

Description

Two related bugs in the PNG text chunk parsers: both ReadCompressedTextChunk (zTXt) and ReadInternationalTextChunk (iTXt) throw unhandled IndexOutOfRangeException when given truncated chunk data. Both methods search for null terminators in the data span and then index past the end without bounds checking.

Each crash requires only an 18-byte input (PNG signature + minimal chunk header + 1 byte of data).

These exceptions are not part of the ImageSharp exception hierarchy (ImageFormatException / InvalidImageContentException), so applications following the documented error handling pattern cannot catch them.

The root cause is the same in both methods: after finding a null terminator for the keyword, the code reads subsequent bytes (compression method, compression flag, language tag, etc.) without verifying that enough bytes remain.

Found by coverage-guided fuzzing with SharpFuzz + AFL++.

Steps to Reproduce

Crash 1: zTXt (ReadCompressedTextChunk)

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

// 18 bytes — PNG signature + zTXt chunk with 1 byte of data
byte[] payload = Convert.FromHexString(
    "89504e470d0a1a0a303030307a545874" +
    "3000");

using var stream = new MemoryStream(payload);
using var image = Image.Load<Rgba32>(stream);  // throws IndexOutOfRangeException
System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at SixLabors.ImageSharp.Formats.Png.PngDecoderCore.ReadCompressedTextChunk(
       ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan`1 data)
   at SixLabors.ImageSharp.Formats.Png.PngDecoderCore.Decode[TPixel](
       BufferedReadStream stream, CancellationToken cancellationToken)

Crash 2: iTXt (ReadInternationalTextChunk)

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

// 18 bytes — PNG signature + iTXt chunk with 1 byte of data
byte[] payload = Convert.FromHexString(
    "89504e470d0a1a0a30303030695458743000");

using var stream = new MemoryStream(payload);
using var image = Image.Load<Rgba32>(stream);  // throws IndexOutOfRangeException
System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at SixLabors.ImageSharp.Formats.Png.PngDecoderCore.ReadInternationalTextChunk(
       ImageMetadata metadata, ReadOnlySpan`1 data)
   at SixLabors.ImageSharp.Formats.Png.PngDecoderCore.Decode[TPixel](
       BufferedReadStream stream, CancellationToken cancellationToken)

Root Cause & Suggested Fix

Both methods need bounds checks after finding the keyword null terminator.

ReadCompressedTextChunk — after finding the keyword, verify at least 1 more byte exists for the compression method:

int keywordEnd = data.IndexOf((byte)0);
if (keywordEnd < 0 || keywordEnd + 2 > data.Length)
{
    return; // not enough data for keyword + null + compression method
}

ReadInternationalTextChunk — after finding the keyword, verify at least 2 more bytes exist for compression flag and compression method:

int keywordEnd = data.IndexOf((byte)0);
if (keywordEnd < 0 || keywordEnd + 4 > data.Length)
{
    return; // not enough data for keyword + null + flag + method + language
}

Additionally, the second IndexOf for the translated keyword null terminator (after the language tag) is not checked for -1. If the data is truncated after the language tag, translatedKeywordLength will be -1, causing data.Slice(translatedKeywordStartIdx, -1) to throw ArgumentOutOfRangeException. Add a guard:

int translatedKeywordLength = data[translatedKeywordStartIdx..].IndexOf((byte)0);
if (translatedKeywordLength < 0)
{
    return;
}

Images

N/A — the reproductions are fully inline above (18-byte constructed PNGs).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions