Corrected bug in AVR GDB memory access command handlers, which allowed GDB to perform out-of-bounds accesses

This commit is contained in:
Nav
2024-10-26 19:26:56 +01:00
parent cb8e5f1d24
commit 08af052ba9
2 changed files with 51 additions and 8 deletions

View File

@@ -52,6 +52,38 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
*/ */
auto accessibleBytes = Targets::TargetMemorySize{0}; auto accessibleBytes = Targets::TargetMemorySize{0};
for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) { for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) {
/*
* GDB assumes that it can only access SRAM (including mapped IO, GPRs, etc), EEPROM and Flash.
* So when it probes the stack (see comment below RE stack probing), it expects the server to
* reject any attempts to access memory that resides after SRAM, on the SRAM (data) address space.
*
* The problem with this is that there are some AVR targets that have memory segments after the
* SRAM segment, on the same address space. For example, the AVR128DA48 has the "mapped_progmem"
* segment, which comes right after the SRAM segment.
*
* And what if the EEPROM segment (which sometimes resides on the data address space), comes after
* the SRAM segment? We must account for all of this.
*
* Fortunately, mapped IO and GPR segments always come before SRAM (this is confirmed via TDF
* validation), so we can still allow GDB to access to those segments.
*
* For the reasons mentioned above, when GDB attempts to access memory in the data address space, from
* a segment which comes after the SRAM segment, and is not EEPROM, we just assume that its intention
* was to access SRAM only, and return an error for any out-of-bounds access attempts.
*/
if (
this->addressSpaceDescriptor == gdbTargetDescriptor.sramAddressSpaceDescriptor
&& memorySegmentDescriptor->type != Targets::TargetMemorySegmentType::RAM
&& memorySegmentDescriptor->type != Targets::TargetMemorySegmentType::EEPROM
&& memorySegmentDescriptor->addressRange.startAddress > gdbTargetDescriptor.sramMemorySegmentDescriptor.addressRange.endAddress
) {
/*
* Ignore this memory segment, as it resides beyond the SRAM segment and is not EEPROM, so it's
* unlikely GDB actually wanted to access it.
*/
continue;
}
if (!memorySegmentDescriptor->debugModeAccess.readable) { if (!memorySegmentDescriptor->debugModeAccess.readable) {
throw Exception{ throw Exception{
"Attempted to access restricted memory segment (" + memorySegmentDescriptor->key "Attempted to access restricted memory segment (" + memorySegmentDescriptor->key
@@ -62,19 +94,15 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
accessibleBytes += memorySegmentDescriptor->addressRange.intersectingSize(addressRange); accessibleBytes += memorySegmentDescriptor->addressRange.intersectingSize(addressRange);
} }
if (accessibleBytes < this->bytes) {
/* /*
* GDB will sometimes request an excess of up to two bytes outside the memory segment address range, even * GDB has requested memory that, at least partially, does not reside in any known/accessible memory
* though we provide it with a memory map. I don't know why it does this, but I do know that we must * segment.
* tolerate it, otherwise GDB will moan.
*/
if (accessibleBytes < this->bytes && (this->bytes - accessibleBytes) > 2) {
/*
* GDB has requested memory that, at least partially, does not reside in any known memory segment.
* *
* This could be a result of GDB being configured to generate backtraces past the main function and * This could be a result of GDB being configured to generate backtraces past the main function and
* the internal entry point of the application. This means that GDB will attempt to walk down the stack * the internal entry point of the application. This means that GDB will attempt to walk down the stack
* to identify every frame. The problem is that GDB doesn't really know where the stack begins, so it * to identify every frame. The problem is that GDB doesn't really know where the stack begins, so it
* probes the target by continuously issuing read memory commands until the server responds with an * probes the stack by continuously issuing read memory commands until the server responds with an
* error. * error.
* *
* CLion seems to enable this by default. Somewhere between CLion 2021.1 and 2022.1, it began issuing * CLion seems to enable this by default. Somewhere between CLion 2021.1 and 2022.1, it began issuing

View File

@@ -45,6 +45,21 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
auto accessibleBytes = Targets::TargetMemorySize{0}; auto accessibleBytes = Targets::TargetMemorySize{0};
for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) { for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) {
if (
this->addressSpaceDescriptor == gdbTargetDescriptor.sramAddressSpaceDescriptor
&& memorySegmentDescriptor->type != Targets::TargetMemorySegmentType::RAM
&& memorySegmentDescriptor->type != Targets::TargetMemorySegmentType::EEPROM
&& memorySegmentDescriptor->addressRange.startAddress > gdbTargetDescriptor.sramMemorySegmentDescriptor.addressRange.endAddress
) {
/*
* Ignore this memory segment, as it resides beyond the SRAM segment and is not EEPROM, so it's
* unlikely GDB actually wanted to access it.
*
* See the comment in the ReadMemory command packet for more.
*/
continue;
}
if (!memorySegmentDescriptor->debugModeAccess.writeable) { if (!memorySegmentDescriptor->debugModeAccess.writeable) {
throw Exception{ throw Exception{
"Attempted to access restricted memory segment (" + memorySegmentDescriptor->key "Attempted to access restricted memory segment (" + memorySegmentDescriptor->key