Skip to content

[C#] IndexOutOfRangeException instead of InvalidProtocolBufferException on truncated messages #26856

@pawlos

Description

@pawlos

What version of protobuf and what language are you using?

Version: Google.Protobuf 3.34.1 (latest stable). Also confirmed against main at commit 514aceb (2026-04-11) - csharp/src/Google.Protobuf/ParsingPrimitives.cs:685 is unchanged across these versions.

Language: C#

What supported operating system version are you using (e.g. Linux, Windows) ?

Reproduced on Linux. The affected code is pure managed, so this should also reproduce on Windows/macOS.

What supported runtime / compiler version are you using (e.g. python version, gcc version)

.NET 10.0 SDK

What did you do?

  1. Create a .NET console app targeting net8.0 or later, add Google.Protobuf 3.34.1
  2. Paste the following code:
using Google.Protobuf;
using Google.Protobuf.Reflection;

var inputs = new[]
{
    ("Varint32",       "2affffffff672acc"),
    ("Varint64",       "42fcffffff57d801"),
    ("LittleEndian32", "3affffffff67b534"),
    ("LittleEndian64", "42ffffffff673934"),
};

foreach (var (name, hex) in inputs)
{
    try
    {
        FileDescriptorProto.Parser.ParseFrom(Convert.FromHexString(hex));
        Console.WriteLine($"{name}: OK");
    }
    catch (InvalidProtocolBufferException)
    {
        Console.WriteLine($"{name}: InvalidProtocolBufferException (expected)");
    }
    catch (IndexOutOfRangeException)
    {
        Console.WriteLine($"{name}: IndexOutOfRangeException (bug)");
    }
}
  1. Run the app.

What did you expect to see

All four inputs are malformed / truncated Protocol Buffers messages. ParseFrom should throw InvalidProtocolBufferException (the documented exception type for parse failures), letting callers handle them with a single catch.

What did you see instead?

All four inputs throw System.IndexOutOfRangeException from ParsingPrimitives.ReadRawByte. Callers that wrap ParseFrom in catch (InvalidProtocolBufferException) - the idiomatic pattern - will not catch it.

Output:

Varint32: IndexOutOfRangeException (bug)
Varint64: IndexOutOfRangeException (bug)
LittleEndian32: IndexOutOfRangeException (bug)
LittleEndian64: IndexOutOfRangeException (bug)

Representative stack trace (Varint32 case):

System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at Google.Protobuf.ParsingPrimitives.ReadRawByte(ReadOnlySpan`1& buffer, ParserInternalState& state)
   at Google.Protobuf.ParsingPrimitives.ParseRawVarint32SlowPath(ReadOnlySpan`1& buffer, ParserInternalState& state)
   at Google.Protobuf.ParsingPrimitives.ParseRawVarint32(ReadOnlySpan`1& buffer, ParserInternalState& state)
   at Google.Protobuf.FieldCodec.<>c.<ForString>b__16_0(ParseContext& ctx)
   at Google.Protobuf.Collections.RepeatedField`1.AddEntriesFrom(ParseContext& ctx, FieldCodec`1 codec)
   at Google.Protobuf.Reflection.EnumDescriptorProto...InternalMergeFrom(ParseContext& input)
   ...
   at Google.Protobuf.MessageParser`1.ParseFrom(Byte[] data)

The three other inputs produce structurally identical stacks ending in ParseRawVarint64SlowPath, ParseRawLittleEndian32SlowPath, and ParseRawLittleEndian64SlowPath respectively. All four reach the same final frame: ParsingPrimitives.ReadRawByte at buffer[state.bufferPos++].

Anything else we should know about your project / environment

Each of the four inputs is a two-level message: FileDescriptorProto containing a length-delimited inner message whose declared length is a 5-byte varint close to int.MaxValue. The trailing bytes of each input drive the inner sub-parser down a different ParseRaw*SlowPath — but the four paths all end at the same out-of-bounds read in ReadRawByte, so they likely share a single root cause in how parser state is maintained after consuming the inner length varint.

Found via coverage-guided fuzzing with AFL++ and SharpFuzz.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions