Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,25 @@ internal static EnvelopeItem FromMetric(TraceMetric metric)
?? throw new InvalidOperationException("Envelope item header is malformed.");
}

private static int GetPayloadBufferLength(Stream stream, long? payloadLength)
{
var remaining = stream.Length - stream.Position;
var length = payloadLength ?? remaining;

if (length < 0 || length > remaining)
{
throw new InvalidDataException(
$"Envelope item declares a payload of {length} bytes but only {remaining} remain.");
}

if (length > int.MaxValue)
{
throw new InvalidDataException($"Envelope item payload of {length} bytes is too large to buffer.");
}

return (int)length;
}

private static async Task<ISerializable> DeserializePayloadAsync(
Stream stream,
IReadOnlyDictionary<string, object?> header,
Expand All @@ -460,7 +479,7 @@ private static async Task<ISerializable> DeserializePayloadAsync(
// Event
if (string.Equals(payloadType, TypeValueEvent, StringComparison.OrdinalIgnoreCase))
{
var bufferLength = (int)(payloadLength ?? stream.Length);
var bufferLength = GetPayloadBufferLength(stream, payloadLength);
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
var sentryEvent = Json.Parse(buffer, SentryEvent.FromJson);

Expand All @@ -470,7 +489,7 @@ private static async Task<ISerializable> DeserializePayloadAsync(
// Transaction
if (string.Equals(payloadType, TypeValueTransaction, StringComparison.OrdinalIgnoreCase))
{
var bufferLength = (int)(payloadLength ?? stream.Length);
var bufferLength = GetPayloadBufferLength(stream, payloadLength);
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
var transaction = Json.Parse(buffer, SentryTransaction.FromJson);

Expand All @@ -480,7 +499,7 @@ private static async Task<ISerializable> DeserializePayloadAsync(
// Session
if (string.Equals(payloadType, TypeValueSession, StringComparison.OrdinalIgnoreCase))
{
var bufferLength = (int)(payloadLength ?? stream.Length);
var bufferLength = GetPayloadBufferLength(stream, payloadLength);
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
var sessionUpdate = Json.Parse(buffer, SessionUpdate.FromJson);

Expand All @@ -490,7 +509,7 @@ private static async Task<ISerializable> DeserializePayloadAsync(
// Client Report
if (string.Equals(payloadType, TypeValueClientReport, StringComparison.OrdinalIgnoreCase))
{
var bufferLength = (int)(payloadLength ?? stream.Length);
var bufferLength = GetPayloadBufferLength(stream, payloadLength);
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
var clientReport = Json.Parse(buffer, ClientReport.FromJson);

Expand Down
96 changes: 96 additions & 0 deletions test/Sentry.Tests/Protocol/Envelopes/EnvelopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,92 @@ await Assert.ThrowsAnyAsync<Exception>(
async () => await Envelope.DeserializeAsync(input));
}

[Fact]
public async Task Deserialization_ItemLengthLongerThanStream_Throws()
{
// Arrange
using var input = """
{"event_id":"12c2d058d58442709aa2eca08bf20986"}
{"type":"event","length":1000000}
{"message":"hello"}

""".ToMemoryStream();

// Act & assert
await Assert.ThrowsAsync<InvalidDataException>(
async () => await Envelope.DeserializeAsync(input));
}

[Fact]
public async Task Deserialization_ItemLengthOverflowingInt_Throws()
{
// Arrange
using var input = """
{"event_id":"12c2d058d58442709aa2eca08bf20986"}
{"type":"event","length":3000000000}
{"message":"hello"}

""".ToMemoryStream();

// Act & assert
await Assert.ThrowsAsync<InvalidDataException>(
async () => await Envelope.DeserializeAsync(input));
}

[Fact]
public async Task Deserialization_NegativeItemLength_Throws()
{
// Arrange
using var input = """
{"event_id":"12c2d058d58442709aa2eca08bf20986"}
{"type":"event","length":-1}
{"message":"hello"}

""".ToMemoryStream();

// Act & assert
await Assert.ThrowsAsync<InvalidDataException>(
async () => await Envelope.DeserializeAsync(input));
}

[Fact]
public async Task Deserialization_ItemWithoutLengthOnOversizedStream_Throws()
{
// Arrange
var envelope = """
{"event_id":"12c2d058d58442709aa2eca08bf20986"}
{"type":"event"}
{"message":"hello"}

""";

using var input = new OversizedStream(Encoding.UTF8.GetBytes(envelope));

// Act & assert
await Assert.ThrowsAsync<InvalidDataException>(
async () => await Envelope.DeserializeAsync(input));
}

[Fact]
public async Task Deserialization_BufferedItemWithoutLength_Success()
{
// Arrange
var serialized = await Envelope.FromEvent(new SentryEvent())
.SerializeToStringAsync(_testOutputLogger, _fakeClock);

var lines = serialized.Split('\n');
lines[1] = """{"type":"event"}""";

using var input = string.Join("\n", lines).ToMemoryStream();

// Act
using var envelope = await Envelope.DeserializeAsync(input);

// Assert
envelope.Items.Should().HaveCount(1);
envelope.Items[0].TryGetType().Should().Be("event");
}

[Fact]
public void FromEvent_Header_IncludesSdkInformation()
{
Expand Down Expand Up @@ -1137,4 +1223,14 @@ public void FromAttachment_ValidAttachment_CreatesEnvelope()
envelope.Items[0].Header["filename"].Should().Be("test.txt");
envelope.Items[0].Header["content_type"].Should().Be("text/plain");
}

private sealed class OversizedStream : MemoryStream
{
public OversizedStream(byte[] buffer)
: base(buffer)
{
}

public override long Length => 3_000_000_000;
}
}
Loading