Skip to content

Consider checking the size of the item returned by IBufferWriter.GetMemory to avoid memory corruption if it is too small #2171

@clivetong

Description

@clivetong

Bug description

The buffer writer and the message pack code work at a low level, with the message pack code expecting the returned buffer to be at least as big as the sizeHint. If it isn't then you can get memory corruption and crashes.

Repro steps


using System.Buffers;
using MessagePack;
using MessagePack.Resolvers;

Foo[] obj = [new Foo(), new Foo()];

var messagePackBufferWriter = new SizeTrackingBufferWriter();
    MessagePackSerializer.Serialize(
        messagePackBufferWriter,
        obj,
        MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolverAllowPrivate.Instance));

    public class SizeTrackingBufferWriter : IBufferWriter<byte>
    {
        private static byte[] _buffer = new byte[10];


        public void Advance(int count)
        {
        }

        public Memory<byte> GetMemory(int sizeHint = 0)
        {
          
            return _buffer.AsMemory(0);
        }

        public Span<byte> GetSpan(int sizeHint = 0)
        {
            return _buffer.AsSpan(0);
        }
    }

    class Foo
    {
        public string s_Foo = "123456789012345678901234";
    }

Expected behavior

When I run this I see two calls to GetMemory - the first passes a sizeHint of 0 and the second passes a sizeHint of 80.
The code returns a buffer of size 10 and this violates the contract.
I'd expect an exception, but see heap corruption.

Actual behavior

If I watch the buffer object in the memory view of the VS debugger, I see the type handle of the next object on the heap being corrupted by the code

                int byteCount = StringEncoding.UTF8.GetBytes(pValue, value.Length, pBuffer + useOffset, bufferSize);

I'd expect the code to check the returned buffer, rather than writing off the end of it.

        internal static Memory<byte> GetMemoryCheckResult(this IBufferWriter<byte> bufferWriter, int size = 0)
        {
            var memory = bufferWriter.GetMemory(size);
            if (memory.IsEmpty)
            {
                throw new InvalidOperationException("The underlying IBufferWriter<byte>.GetMemory(int) method returned an empty memory block, which is not allowed. This is a bug in " + bufferWriter.GetType().FullName);
            }

            if (size > 0 && memory.Length < size)
            {
                throw new InvalidOperationException("The underlying IBufferWriter<byte>.GetMemory(int) returned a buffer that is too small. This is a bug in " + bufferWriter.GetType().FullName);
            }

We saw segmentation faults in our Linux containers.

  • Version used: main
  • Runtime: (e.g. .NET Framework, .NET Core, Unity, mono, etc.).NET 9.0.200

Additional context

Add any other context about the problem here.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions