Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace Microsoft.CodeAnalysis.BinaryParsers.Dwarf
{
/// <summary>
/// Exception thrown when an attempt is made to read past the end of a DWARF data buffer.
/// </summary>
public class DwarfBufferOverreadException : InvalidOperationException
{
public DwarfBufferOverreadException(uint position, uint requestedBytes, int bufferLength)
: base("Attempted to read past end of DWARF data buffer.")
{
Position = position;
RequestedBytes = requestedBytes;
BufferLength = bufferLength;
}

/// <summary>
/// Gets the position in the buffer at which the over-read was detected.
/// </summary>
public uint Position { get; }

/// <summary>
/// Gets the number of bytes that were requested.
/// </summary>
public uint RequestedBytes { get; }

/// <summary>
/// Gets the total length of the underlying buffer.
/// </summary>
public int BufferLength { get; }
}
}
79 changes: 71 additions & 8 deletions src/BinaryParsers/ElfBinary/Dwarf/DwarfMemoryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;

using Microsoft.CodeAnalysis.BinaryParsers.Dwarf;

namespace Microsoft.CodeAnalysis.BinaryParsers.Dwarf
{
/// <summary>
Expand Down Expand Up @@ -60,6 +62,14 @@ public bool IsEnd
}
}

private void EnsureAvailable(uint bytesToRead)
{
if (bytesToRead > (uint)Data.Length || Position > (uint)Data.Length - bytesToRead)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First part in front of || seems redundant (it's equivalent of second part, with Position = 0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if Position is 0, there might be a scenario where we want to read more bytes than length (Data.Length = 4 and bytesToRead = 8 -> that is the kind of scenario where first part is necessary

{
throw new DwarfBufferOverreadException(Position, bytesToRead, Data.Length);
}
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
Expand All @@ -74,6 +84,7 @@ public void Dispose()
/// </summary>
public byte Peek()
{
EnsureAvailable(1);
return Data[Position];
}

Expand All @@ -83,6 +94,7 @@ public byte Peek()
/// <typeparam name="T">Type of the structure to be read</typeparam>
public T ReadStructure<T>()
Comment thread
Sasinkas marked this conversation as resolved.
{
EnsureAvailable((uint)Marshal.SizeOf<T>());
T result = Marshal.PtrToStructure<T>((nint)(pointer + Position));
Position += (uint)Marshal.SizeOf<T>();
return result;
Expand Down Expand Up @@ -129,9 +141,9 @@ public string ReadString()
Position += (uint)result.Length + 1;
return result;
}
catch (Exception ex)
catch (Exception ex) when (ex is ArgumentException || ex is AccessViolationException)
{
throw new InvalidOperationException("Failed to read string from memory.", ex);
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}
}

Expand All @@ -140,6 +152,7 @@ public string ReadString()
/// </summary>
public byte ReadByte()
{
EnsureAvailable(1);
return Data[Position++];
}

Expand All @@ -148,6 +161,7 @@ public byte ReadByte()
/// </summary>
public ushort ReadUshort()
{
EnsureAvailable(2);
ushort result = (ushort)Marshal.ReadInt16(pointer, (int)Position);

Position += 2;
Expand All @@ -167,6 +181,7 @@ public uint ReadThreeBytes()
/// </summary>
public uint ReadUint()
{
EnsureAvailable(4);
uint result = (uint)Marshal.ReadInt32(pointer, (int)Position);

Position += 4;
Expand All @@ -178,6 +193,7 @@ public uint ReadUint()
/// </summary>
public ulong ReadUlong()
{
EnsureAvailable(8);
ulong result = (ulong)Marshal.ReadInt64(pointer, (int)Position);

Position += 8;
Expand Down Expand Up @@ -205,15 +221,38 @@ public ulong ReadUlong(uint size)
/// </summary>
public ulong ULEB128()
{
if (Position >= Data.Length)
{
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}
Comment thread
Sasinkas marked this conversation as resolved.

ulong x = 0;
int shift = 0;

while ((Data[Position] & 0x80) != 0)
while (true)
{
x |= (uint)((Data[Position] & 0x7f) << shift);
if (Position >= Data.Length)
{
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}

byte b = Data[Position];

if ((b & 0x80) == 0)
{
break;
}

x |= (uint)((b & 0x7f) << shift);
shift += 7;
Position++;
}

if (Position >= Data.Length)
{
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}
Comment thread
Sasinkas marked this conversation as resolved.

x |= (uint)(Data[Position] << shift);
Position++;
return x;
Expand All @@ -224,17 +263,41 @@ public ulong ULEB128()
/// </summary>
public uint SLEB128()
Comment thread
Sasinkas marked this conversation as resolved.
Comment thread
Sasinkas marked this conversation as resolved.
{
if (Position >= Data.Length)
{
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}

int x = 0;
int shift = 0;

while ((Data[Position] & 0x80) != 0)
while (true)
{
x |= (Data[Position] & 0x7f) << shift;
if (Position >= Data.Length)
{
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}

byte b = Data[Position];

if ((b & 0x80) == 0)
{
break;
}

x |= (b & 0x7f) << shift;
shift += 7;
Position++;
}
x |= Data[Position] << shift;
if ((Data[Position] & 0x40) != 0)

if (Position >= Data.Length)
{
throw new DwarfBufferOverreadException(Position, 1, Data.Length);
}

byte last = Data[Position];
x |= last << shift;
if ((last & 0x40) != 0)
{
x |= -(1 << (shift + 7)); // sign extend
}
Expand Down
41 changes: 32 additions & 9 deletions src/BinaryParsers/ElfBinary/Dwarf/DwarfSymbolProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,24 +93,47 @@ internal static DwarfCompilationUnit ParseOneCompilationUnitByOffset(IDwarfBinar
}

debugDataReader.Position = offset;
return new DwarfCompilationUnit(dwarfBinary,
debugDataReader,
debugDataDescriptionReader,
debugStringsReader,
debugLineStringsReader,
debugStringOffsetsReader,
addressNormalizer);
try
{
return new DwarfCompilationUnit(dwarfBinary,
debugDataReader,
debugDataDescriptionReader,
debugStringsReader,
debugLineStringsReader,
debugStringOffsetsReader,
addressNormalizer);
}
catch (InvalidOperationException)
{
// Malformed or truncated DWARF data at this offset; treat as no more
// compilation units instead of failing the entire analysis.
return null;
}
}

internal static List<int> ParseDebugStringOffsets(byte[] debugStringOffsets, bool is64bit)
{
if (debugStringOffsets == null || debugStringOffsets.Length == 0)
{
return new List<int>();
}

using var debugStringOffsetsReader = new DwarfMemoryReader(debugStringOffsets);
var stringOffsets = new List<int>();

while (!debugStringOffsetsReader.IsEnd)
{
int offset = debugStringOffsetsReader.ReadOffset(is64bit);
stringOffsets.Add(offset);
try
{
int offset = debugStringOffsetsReader.ReadOffset(is64bit);
stringOffsets.Add(offset);
}
catch (InvalidOperationException)
{
// Truncated DWARF string offset table; stop reading further
// entries and return what we've successfully parsed.
break;
}
}

return stringOffsets;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;

using FluentAssertions;
Expand Down Expand Up @@ -225,5 +225,72 @@ public void Peek_DoesNotAdvancePosition()
reader.Peek().Should().Be(0x42);
reader.Position.Should().Be(0);
}

[Fact]
public void Peek_WhenPositionAtEnd_ThrowsDwarfBufferOverreadException()
{
using var reader = new DwarfMemoryReader(new byte[] { 0x01 });
reader.Position = 1; // exactly at end

Assert.Throws<DwarfBufferOverreadException>(() => reader.Peek());
reader.Position.Should().Be(1);
}

[Fact]
public void ReadByte_WhenPositionAtEnd_ThrowsDwarfBufferOverreadException()
{
using var reader = new DwarfMemoryReader(new byte[] { 0x01 });
reader.Position = 1; // exactly at end

Assert.Throws<DwarfBufferOverreadException>(() => reader.ReadByte());
reader.Position.Should().Be(1);
}

[Fact]
public void ReadUshort_WhenNotEnoughBytesRemain_ThrowsDwarfBufferOverreadException()
{
using var reader = new DwarfMemoryReader(new byte[] { 0x01 }); // only one byte available

Assert.Throws<DwarfBufferOverreadException>(() => reader.ReadUshort());
reader.Position.Should().Be(0);
}

[Fact]
public void ReadUint_WhenNotEnoughBytesRemain_ThrowsInvalidOperationException()
{
using var reader = new DwarfMemoryReader(new byte[] { 0x01, 0x02, 0x03 }); // three bytes

Assert.Throws<DwarfBufferOverreadException>(() => reader.ReadUint());
reader.Position.Should().Be(0);
}

[Fact]
public void ReadUlong_WhenNotEnoughBytesRemain_ThrowsDwarfBufferOverreadException()
{
using var reader = new DwarfMemoryReader(new byte[] { 0x01, 0x02, 0x03, 0x04 }); // four bytes

Assert.Throws<DwarfBufferOverreadException>(() => reader.ReadUlong());
reader.Position.Should().Be(0);
}

[Fact]
public void ULEB128_WhenContinuationBitSetButNoMoreBytes_ThrowsDwarfBufferOverreadException()
{
// 0x80 has continuation bit set, but no following byte per DWARF4/5 spec: invalid/truncated sequence.
using var reader = new DwarfMemoryReader(new byte[] { 0x80 });

Assert.Throws<DwarfBufferOverreadException>(() => reader.ULEB128());
reader.Position.Should().Be(1);
}

[Fact]
public void SLEB128_WhenContinuationBitSetButNoMoreBytes_ThrowsDwarfBufferOverreadException()
{
// 0x80 has continuation bit set, but no following byte per DWARF4/5 spec: invalid/truncated sequence.
using var reader = new DwarfMemoryReader(new byte[] { 0x80 });

Assert.Throws<DwarfBufferOverreadException>(() => reader.SLEB128());
reader.Position.Should().Be(1);
}
}
}
Loading