Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Denial of Service Vulnerability in TCP Packet Handling #36750

Closed
BoB13-Matter opened this issue Dec 6, 2024 · 0 comments · Fixed by #36751
Closed

[BUG] Denial of Service Vulnerability in TCP Packet Handling #36750

BoB13-Matter opened this issue Dec 6, 2024 · 0 comments · Fixed by #36751
Labels
bug Something isn't working needs triage

Comments

@BoB13-Matter
Copy link
Contributor

Reproduction steps

  1. Run chip-all-clusters-app.

  2. Configure the following parameters in poc.py:
    poc.py

    • target_ip and target_port: The IP and port of the chip-all-clusters-app instance.
    • interface: The name of your local network interface.
  3. Execute poc.py with the command: sudo python3 poc.py.

  4. Observe that the chip-all-clusters-app crashes, producing the following crash log:
    crash_log.txt

Summary

A vulnerability in the TCP-based Matter packet handling allows an unauthenticated attacker to trigger a crash by sending a single malformed packet. The issue stems from improper management of packet during message parsing, leading to a null pointer dereference. This affects all Matter implementations that rely on the TCP packet handling logic and can result in a complete Denial of Service (DoS).

Description and Analysis

  1. Parsing the Packet Length

    TCP-based Matter packets encode the payload length in the first 4 bytes. In the TCPBase::ProcessReceivedBuffer function, this length is read using the Read() method and stored in the messageSize variable.

    CHIP_ERROR TCPBase::ProcessReceivedBuffer(Inet::TCPEndPoint * endPoint, const PeerAddress & peerAddress,
                                              System::PacketBufferHandle && buffer)
    {
        ActiveTCPConnectionState * state = FindActiveConnection(endPoint);
        VerifyOrReturnError(state != nullptr, CHIP_ERROR_INTERNAL);
        state->mReceived.AddToEnd(std::move(buffer));
    
        while (!state->mReceived.IsNull())
        {
            uint8_t messageSizeBuf[kPacketSizeBytes];
            CHIP_ERROR err = state->mReceived->Read(messageSizeBuf);
            if (err == CHIP_ERROR_BUFFER_TOO_SMALL)
            {
                // Not enough data to read the message size. Wait for more data.
                return CHIP_NO_ERROR;
            }
            if (err != CHIP_NO_ERROR)
            {
                return err;
            }
            uint32_t messageSize = LittleEndian::Get32(messageSizeBuf);
  2. Consuming Length Bytes

    Before the ProcessSingleMessage function is invoked, the 4-byte length field is consumed using the Consume() method.

        state->mReceived.Consume(kPacketSizeBytes); // kPacketSizeBytes == 4
        ReturnErrorOnFailure(ProcessSingleMessage(peerAddress, state, messageSize));
    void Consume(size_t aConsumeLength) { mBuffer = mBuffer->Consume(aConsumeLength); }
  3. Invoke FreeHead Function

    If the length to consume (aConsumeLength) exceeds or equals the remaining buffer length (kLength), the FreeHead() function is invoked.

    PacketBuffer * PacketBuffer::Consume(size_t aConsumeLength)
    {
        PacketBuffer * lPacket = this;
    
        while (lPacket != nullptr && aConsumeLength > 0)
        {
            const size_t kLength = lPacket->DataLength();
    
            if (aConsumeLength >= kLength) // 4 >= 4
            {
                lPacket        = PacketBuffer::FreeHead(lPacket);
                aConsumeLength = aConsumeLength - kLength;
            }
            else
            {
                lPacket->ConsumeHead(aConsumeLength);
                break;
            }
        }
    
        return lPacket;
    }
  4. Return nullptr

    When ChainedBuffer is not present, lNextPacket becomes nullptr. This value is returned, causing state->mReceived's mBuffer to become nullptr.

    PacketBuffer * PacketBuffer::FreeHead(PacketBuffer * aPacket)
    {
        PacketBuffer * lNextPacket = aPacket->ChainedBuffer(); // lNextPacket = nullptr
        aPacket->next              = nullptr;
        PacketBuffer::Free(aPacket);
        return lNextPacket; // return nullptr
    }
  5. Crash via Null Pointer Dereference

    When ProcessSingleMessage is executed, state->mReceived is nullptr, leading to a null pointer dereference and a crash.

    CHIP_ERROR TCPBase::ProcessSingleMessage(const PeerAddress & peerAddress, ActiveTCPConnectionState * state, size_t messageSize)
    {
        // ...
        if (state->mReceived->DataLength() == messageSize) // Crash: null pointer dereference
        {

    Debug Output:

    pwndbg> p state->mReceived
    $5 = {
        mBuffer = 0x0
    }

Proposed Solution

To prevent this issue, a null pointer check should be added for state->mReceived before invoking ProcessSingleMessage. This ensures the function is not called when state->mReceived is nullptr.

    state->mReceived.Consume(kPacketSizeBytes);
    VerifyOrReturnError(!state->mReceived.IsNull(), CHIP_ERROR_MESSAGE_INCOMPLETE); // Add null pointer verification
    ReturnErrorOnFailure(ProcessSingleMessage(peerAddress, state, messageSize));

Bug prevalence

always

GitHub hash of the SDK that was being used

ffbc362

Platform

core

Platform Version(s)

all versions with TCP support

Anything else?

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs triage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant