Skip to content

BasicGetResult body memory safety in 6.x+ #994

@Pliner

Description

@Pliner

Hi,

It looks I've accidentally found an amusing bug. Let me start from this example(by the way, I'm using v6.2.1):

using System;
using System.Collections.Generic;
using System.Threading;
using RabbitMQ.Client;


namespace DotNetExperiments
{
    public static class Program
    {
        private static readonly ReadOnlyMemory<byte> SentBody = new byte[] {42, 42, 42, 42, 42};
        
        public static void Main()
        {
            // Boostrap stuff
            var connectionFactory = new ConnectionFactory
            {
                Uri = new Uri("amqps://apwlvbny:ZPrB0qqbBkBE0jOwUAHERRXL39fog2jc@rattlesnake.rmq.cloudamqp.com/apwlvbny")
            };
            var connection = connectionFactory.CreateConnection();
            var model = connection.CreateModel();
            var queue = Guid.NewGuid().ToString("N");
            model.QueueDeclare(queue, false, false);

            // Let's store all received bodies
            var receivedBodies = new List<ReadOnlyMemory<byte>>();
            while (true)
            {
                Thread.Sleep(100);

                // Then every iteration we recheck all received bodies: they should match with sentBody 
                foreach (var receivedBody in receivedBodies) CheckReceivedBody(receivedBody);

                model.BasicPublish("", queue, true, null, SentBody);
                var getResult = model.BasicGet(queue, true);
                if (getResult == null)
                    continue;

                // Store received body
                receivedBodies.Add(getResult.Body);
            }
        }

        private static void CheckReceivedBody(ReadOnlyMemory<byte> receivedBody)
        {
            if (receivedBody.Span.SequenceEqual(SentBody.Span))
                return;
            
            Console.WriteLine($"Mismatch: {string.Join(",", receivedBody.Span.ToArray())} != {string.Join(",", SentBody.Span.ToArray())}");
        }
    }
}

After a couple of seconds mismatches have been found:

Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42
Mismatch: 8,0,0,0,0 != 42,42,42,42,42

An array under BasicGetResult.Body was reused.

The reason of it might be the following: after a completion of BasicGet RPC continuation, a command is disposed and an array under BasicGetResult.Body is returned to ArrayPool. This makes usage of BasicGetResult.Body unpredictable at least(EasyNetQ/EasyNetQ#1153) because it is unsafe to use BasicGetResult.Body right after model.BasicGet returns it.

It looks like the only fix is to copy a body a bit before a completion of a continuation.

Could you please confirm the issue?

Metadata

Metadata

Assignees

No one assigned

    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