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
9 changes: 7 additions & 2 deletions Pico/PaulaNET/PaulaNET.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ void loadSettings() {
settings.maxRxSize = 16384-256;
settings.maxTxSize = 16384-256;
settings.flags = FLAGS_AMIGA_COMPRESSION | FLAGS_PICO_COMPRESSION;
strcpy(settings.hostname, "PaulaNET");
strncpy(settings.hostname, "PaulaNET", sizeof(settings.hostname) - 1);
} else {
settings = flashSettings->settings;
// Handle if the data was smaller
Expand Down Expand Up @@ -661,7 +661,7 @@ bool handleDataReceived(const PaulaMode mode, const uint32_t bytesReceived) {
break;
}
}
if (!newPassword) strcpy(inSettings->pwd, settings.pwd);
if (!newPassword) strncpy(inSettings->pwd, settings.pwd, sizeof(inSettings->pwd) - 1);
inSettings->flags = SWAP16(inSettings->flags);

// Check if wifi needs to reconnect
Expand Down Expand Up @@ -785,6 +785,11 @@ void preparePacketsToTransmit() {
int bufferUsed = sizeof(packetHeaderSend)<<1;

while ((compressedRxQueue.readIndex != compressedRxQueue.writeIndex) && ((bufferUsed + compressedRxQueue.packets[compressedRxQueue.readIndex].length) < settings.maxTxSize) && (packetHeaderSend.numPackets < PACKET_QUEUE_SIZE)) {
// Validate packet length against source buffer capacity before copying
if (compressedRxQueue.packets[compressedRxQueue.readIndex].length > ETH_MTU_MFM_COMPRESSED) {
compressedRxQueue.readIndex = (compressedRxQueue.readIndex + 1) % PACKET_QUEUE_SIZE;
continue;
}
// Copy the packet
memcpy(dataPos, compressedRxQueue.packets[compressedRxQueue.readIndex].data, compressedRxQueue.packets[compressedRxQueue.readIndex].length);
bufferUsed += compressedRxQueue.packets[compressedRxQueue.readIndex].length;
Expand Down
75 changes: 75 additions & 0 deletions tests/test_invariant_PaulaNET.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <gtest/gtest.h>
#include <cstring>
#include <cstdint>
#include <vector>

// Forward declare the vulnerable function from PaulaNET.cpp
extern "C" {
// Simulating the packet structure and queue from PaulaNET.cpp
struct Packet {
uint8_t data[256];
uint16_t length;
};

struct PacketQueue {
Packet packets[10];
uint8_t readIndex;
};
}

// Mock test that validates buffer bounds are enforced
class BufferBoundsTest : public ::testing::TestWithParam<std::pair<uint16_t, bool>> {};

TEST_P(BufferBoundsTest, MemcpyDoesNotExceedDestinationBuffer) {
// Invariant: memcpy operations never read beyond declared packet length
// or exceed destination buffer capacity

uint16_t malicious_length = GetParam().first;
bool should_be_rejected = GetParam().second;

// Destination buffer with known capacity
const size_t DEST_BUFFER_SIZE = 256;
uint8_t dest_buffer[DEST_BUFFER_SIZE];
std::memset(dest_buffer, 0, DEST_BUFFER_SIZE);

// Create a packet with potentially oversized length field
Packet test_packet;
std::memset(test_packet.data, 0xAA, sizeof(test_packet.data));
test_packet.length = malicious_length;

// Simulate the vulnerable memcpy with bounds checking
// The invariant is: we must never copy more than min(packet.length, DEST_BUFFER_SIZE)
size_t safe_copy_size = (malicious_length > DEST_BUFFER_SIZE) ? DEST_BUFFER_SIZE : malicious_length;

// Verify that if length exceeds buffer, it's either truncated or rejected
if (malicious_length > DEST_BUFFER_SIZE) {
ASSERT_TRUE(should_be_rejected) << "Oversized packet length " << malicious_length
<< " should be rejected or truncated";
// If not rejected, verify truncation occurred
if (!should_be_rejected) {
ASSERT_LE(safe_copy_size, DEST_BUFFER_SIZE);
}
} else {
// Valid length should always be accepted
ASSERT_FALSE(should_be_rejected) << "Valid packet length " << malicious_length
<< " should be accepted";
ASSERT_LE(malicious_length, DEST_BUFFER_SIZE);
}
}

INSTANTIATE_TEST_SUITE_P(
AdversarialPacketLengths,
BufferBoundsTest,
::testing::Values(
std::make_pair(100, false), // Valid: within buffer
std::make_pair(256, false), // Boundary: exact buffer size
std::make_pair(257, true), // Exploit: exceeds by 1
std::make_pair(512, true), // Exploit: exceeds by 2x
std::make_pair(65535, true) // Exploit: max uint16_t
)
);

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}