Skip to content

Null pointer dereference on allocation failure in quicforward #5806

@MarkedMuichiro

Description

@MarkedMuichiro

Describe the bug

In src/tools/forwarder/quicforward.cpp, three allocation failure paths
are unhandled:

  1. In ConnectionCallback (PEER_STREAM_STARTED), MsQuicStream objects are
    allocated with new(std::nothrow) but never null-checked before being
    dereferenced. On allocation failure, PeerStream->Context crashes with a
    null pointer dereference. Additionally, if LocalStream allocation fails,
    the raw stream handle from Event->PEER_STREAM_STARTED.Stream is never
    closed, leaking it.

  2. In ListenerCallback (NEW_CONNECTION), MsQuicConnection objects are
    allocated with new(std::nothrow) but never null-checked before being
    dereferenced. On allocation failure, BackEndConn->Context crashes with a
    null pointer dereference.

  3. In StreamCallback (RECEIVE), if StreamSend returns an unexpected error
    status, CXPLAT_FRE_ASSERT fires without first freeing SendContext, causing
    a resource leak before process termination.

The correct null-check pattern already exists in MsQuicAutoAcceptListener
in msquic.hpp and is not consistently applied in quicforward.cpp.

Affected OS

  • Windows
  • Linux
  • macOS
  • Other (specify below)

Additional OS information

No response

MsQuic version

main

Steps taken to reproduce bug

  1. Run the quicforward tool to establish an active forwarding session.
  2. Trigger an OOM condition during stream or connection setup to cause
    new(std::nothrow) to return null in ConnectionCallback or
    ListenerCallback.
  3. For the third bug: cause StreamSend to return a failure status other than
    QUIC_STATUS_ABORTED or QUIC_STATUS_INVALID_STATE during an active
    forwarding session.

Note: these paths require OOM conditions or fault injection to trigger
reliably and cannot be easily reproduced without a dedicated test harness.

Expected behavior

Allocation failures are handled gracefully. On stream allocation failure, the inbound stream handle is closed cleanly and the connection continues. On connection allocation failure, QUIC_STATUS_OUT_OF_MEMORY is returned and MsQuic refuses the connection cleanly. On unexpected send failure, SendContext is freed before process termination.

Actual outcome

Null pointer dereference and crash when new(std::nothrow) returns null in ConnectionCallback or ListenerCallback. Resource leak before process termination when StreamSend returns an unexpected error in StreamCallback.

Additional details

Root cause traced across three files:

  • quicforward.cpp: allocation results used without null checks
  • msquic.hpp: MsQuicStream and MsQuicConnection constructors have no
    internal null guard on the outer pointer; destructor calls StreamClose/
    ConnectionClose, so delete on a successfully constructed wrapper will
    close the handle — manual close only needed when new returns null
  • listener.c: confirmed that returning QUIC_STATUS_OUT_OF_MEMORY from
    ListenerCallback is safe — MsQuic refuses the connection and retains
    handle ownership on failure returns
  • spinquic.cpp: confirms that PEER_STREAM_STARTED requires explicit
    StreamClose on rejection, not a bare break

I created a fix and will be submit it as a PR referencing this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    good first issueGood for newcomershelp wantedThis task will not be prioritized, but a contribution is welcome.

    Type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions