VYPR
Medium severity4.3NVD Advisory· Published May 30, 2026

CVE-2026-10116

CVE-2026-10116

Description

A security flaw has been discovered in Open5GS up to 2.7.7. This vulnerability affects the function ogs_sbi_xact_add in the library /lib/core/ogs-timer.c of the component ue-authentications Endpoint. Performing a manipulation results in denial of service. The attack may be initiated remotely. The exploit has been released to the public and may be used for attacks. Applying a patch is the recommended action to fix this issue.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A denial-of-service vulnerability in Open5GS up to 2.7.7 allows remote attackers to crash the AUSF by exhausting the timer pool via repeated authentication requests.

Vulnerability

A denial-of-service vulnerability exists in Open5GS up to version 2.7.7, specifically in the AUSF (Authentication Server Function) component. The flaw resides in the function ogs_sbi_xact_add in /lib/core/ogs-timer.c, which creates SBI transactions with response timers. When handling POST /nausf-auth/v1/ue-authentications, if the client resets the HTTP/2 stream, the AUSF frees the local stream and request objects, but the outbound nudm-ueau transaction and its timer remain pending until the SBI client wait timeout. Repeatedly doing this exhausts the finite timer pool (default size 16384, calculated as MAX_NUM_OF_UE * POOL_NUM_PER_UE), causing ogs_sbi_xact_add() to fail and leading to a crash via ogs_assert [1].

Exploitation

An attacker can remotely trigger this vulnerability without authentication by sending repeated bursts of POST /nausf-auth/v1/ue-authentications requests while ensuring that the outbound nudm-ueau requests to generate authentication data are kept hanging (e.g., by not responding or delaying responses) [1]. Each incoming auth request creates an SBI xact with a response timer. If the attacker resets the HTTP/2 stream, the AUSF cleans up the inbound stream but the outbound xact timer remains. Repeating this process quickly exhausts the shared timer pool [1]. The exploit has been publicly released [CVE description].

Impact

Successful exploitation results in a denial of service (DoS) condition, crashing the AUSF process. This prevents legitimate authentication requests from being processed, disrupting 5G core network operations [1]. The crash occurs at ausf_nausf_auth_handle_authenticate() due to ogs_assert(r != OGS_ERROR) after ausf_sbi_discover_and_send() returns OGS_ERROR because timer allocation fails [1].

Mitigation

The fix is implemented in pull request #4578, which cancels pending outbound SBI transactions when the originating stream is closed [2]. Users should upgrade to a patched version of Open5GS that includes this fix. As of the publication date, the fix is available in the repository's master branch; the recommended action is to apply the patch or update to a version beyond 2.7.7 [2]. No workaround is mentioned; the vulnerability is listed as having a public exploit, so immediate patching is advised.

AI Insight generated on May 30, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Open5gs/Open5gsreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <=2.7.7

Patches

1
68ab28cd86bb

sbi: cancel pending outbound xacts on stream close

https://github.com/open5gs/open5gsSukchan LeeMay 24, 2026via nvd-ref
8 files changed · +423 1
  • lib/sbi/context.c+24 0 modified
    @@ -2726,6 +2726,30 @@ void ogs_sbi_xact_remove(ogs_sbi_xact_t *xact)
         if (xact->target_apiroot)
             ogs_free(xact->target_apiroot);
     
    +    /*
    +     * Detach from the originating stream's xact_list, if attached.
    +     *
    +     * Two paths reach here:
    +     *
    +     *   (a) Normal completion: a response arrived (or the send
    +     *       failed early) and the NF calls ogs_sbi_xact_remove().
    +     *       xact_detach() unlinks via the cached to_stream_list
    +     *       head in O(1), no stream lookup needed.
    +     *
    +     *   (b) Stream close: stream_remove_xact_all() (or its MHD
    +     *       counterpart) walks the stream's xact_list and calls
    +     *       ogs_sbi_xact_remove() on each entry. This detach runs
    +     *       first and unlinks the node before the for_each_safe
    +     *       iterator advances; the iterator's cached "next" pointer
    +     *       keeps the loop sound.
    +     *
    +     * Idempotent for transactions that never had an inbound stream
    +     * (NRF discovery initiated by the NF itself, status
    +     * notifications): to_stream_list stays NULL and the helper is
    +     * a no-op.
    +     */
    +    ogs_sbi_server_detach_xact(xact);
    +
         /*
          * Release optional user context attached to the transaction.
          * The transaction owns this memory and is responsible for
    
  • lib/sbi/context.h+36 0 modified
    @@ -286,6 +286,42 @@ typedef struct ogs_sbi_xact_s {
     
         ogs_pool_id_t assoc_stream_id;
     
    +    /*
    +     * Linkage into the server-side stream that triggered this
    +     * outbound transaction.
    +     *
    +     * When the inbound HTTP/2 stream is closed by the peer (e.g.
    +     * RST_STREAM or connection close) before the response from the
    +     * upstream NF arrives, every transaction registered on the
    +     * stream's xact_list is cancelled. This releases the response
    +     * timer back to the pool immediately instead of holding a slot
    +     * until the SBI client wait timeout expires, which is the
    +     * accumulation that leads to timer-pool exhaustion when a peer
    +     * resets streams rapidly while the upstream NF is slow or
    +     * unresponsive (issues #4472 and #4473).
    +     *
    +     * The linkage is managed entirely by lib/sbi:
    +     *   - ogs_sbi_discover_and_send() attaches when assoc_stream_id
    +     *     is set;
    +     *   - ogs_sbi_xact_remove() detaches automatically;
    +     *   - stream/session close drains the list and removes every
    +     *     attached transaction.
    +     *
    +     * to_stream_list is both the attachment flag and the cached
    +     * list head:
    +     *   to_stream_list != NULL
    +     *      <=> to_stream_node is on *to_stream_list
    +     *      <=> assoc_stream_id refers to the owning stream
    +     *
    +     * Caching the list head address lets detach unlink in O(1)
    +     * without re-resolving the stream from assoc_stream_id.
    +     * Transactions with no associated inbound stream (e.g. NRF
    +     * discovery initiated by the NF itself, status notifications)
    +     * leave the whole linkage group zero-initialised.
    +     */
    +    ogs_lnode_t to_stream_node;
    +    ogs_list_t *to_stream_list;
    +
         /*
          * Optional user context attached to this SBI transaction.
          *
    
  • lib/sbi/mhd-server.c+88 0 modified
    @@ -45,6 +45,9 @@ static ogs_sbi_server_t *server_from_stream(ogs_sbi_stream_t *stream);
     static ogs_pool_id_t id_from_stream(ogs_sbi_stream_t *stream);
     static void *stream_find_by_id(ogs_pool_id_t id);
     
    +static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact);
    +static void xact_detach(ogs_sbi_xact_t *xact);
    +
     const ogs_sbi_server_actions_t ogs_mhd_server_actions = {
         server_init,
         server_final,
    @@ -59,6 +62,9 @@ const ogs_sbi_server_actions_t ogs_mhd_server_actions = {
         server_from_stream,
         id_from_stream,
         stream_find_by_id,
    +
    +    xact_attach,
    +    xact_detach,
     };
     
     static void run(short when, ogs_socket_t fd, void *data);
    @@ -110,6 +116,15 @@ typedef struct ogs_sbi_session_s {
          */
         ogs_timer_t             *timer;
     
    +    /*
    +     * Outbound SBI transactions originated by this session. Drained
    +     * on session_remove() so response timers are returned to the
    +     * pool immediately when the inbound HTTP connection goes away.
    +     * See the matching xact_list on the HTTP/2 backend (stream_s in
    +     * lib/sbi/nghttp2-server.c).
    +     */
    +    ogs_list_t              xact_list;
    +
         void *data;
     } ogs_sbi_session_t;
     
    @@ -141,6 +156,8 @@ static ogs_sbi_session_t *session_add(ogs_sbi_server_t *server,
         sbi_sess->request = request;
         sbi_sess->connection = connection;
     
    +    ogs_list_init(&sbi_sess->xact_list);
    +
         sbi_sess->timer = ogs_timer_add(
                 ogs_app()->timer_mgr, session_timer_expired,
                 OGS_UINT_TO_POINTER(sbi_sess->id));
    @@ -160,6 +177,75 @@ static ogs_sbi_session_t *session_add(ogs_sbi_server_t *server,
         return sbi_sess;
     }
     
    +static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact)
    +{
    +    ogs_sbi_session_t *sbi_sess = (ogs_sbi_session_t *)stream;
    +
    +    ogs_assert(sbi_sess);
    +    ogs_assert(xact);
    +
    +    /*
    +     * Invariant: the server-layer wrapper (ogs_sbi_server_attach_xact)
    +     * already filtered out the already-attached case. Reaching the
    +     * backend with to_stream_list set is a programming error.
    +     */
    +    ogs_assert(!xact->to_stream_list);
    +
    +    /*
    +     * Cache the list head so xact_detach() can unlink in O(1).
    +     * to_stream_list serves as both attachment flag and cached head.
    +     */
    +    ogs_list_add(&sbi_sess->xact_list, &xact->to_stream_node);
    +    xact->to_stream_list = &sbi_sess->xact_list;
    +}
    +
    +static void xact_detach(ogs_sbi_xact_t *xact)
    +{
    +    ogs_assert(xact);
    +
    +    /*
    +     * Invariant: the server-layer wrapper (ogs_sbi_server_detach_xact)
    +     * already filtered out the not-attached case. Reaching the
    +     * backend with to_stream_list cleared is a programming error.
    +     */
    +    ogs_assert(xact->to_stream_list);
    +
    +    ogs_list_remove(xact->to_stream_list, &xact->to_stream_node);
    +
    +    xact->to_stream_list = NULL;
    +    xact->assoc_stream_id = OGS_INVALID_POOL_ID;
    +}
    +
    +/*
    + * Cancel outbound SBI transactions originated by this session before
    + * tearing it down. Same rationale as stream_remove_xact_all() on the
    + * HTTP/2 backend: release response timers immediately instead of
    + * holding pool slots until the SBI client wait timeout.
    + */
    +static void session_remove_xact_all(ogs_sbi_session_t *sbi_sess)
    +{
    +    ogs_sbi_xact_t *xact = NULL, *next_xact = NULL;
    +
    +    ogs_assert(sbi_sess);
    +
    +    ogs_list_for_each_entry_safe(
    +            &sbi_sess->xact_list, next_xact, xact, to_stream_node) {
    +        /*
    +         * Logged at error level so the cancellation shows up in
    +         * production traces alongside the upstream NF activity that
    +         * was abandoned. See the matching log in stream_remove() on
    +         * the HTTP/2 backend.
    +         */
    +        ogs_error("Canceling pending outbound SBI transaction "
    +                "on MHD session close [xact:%d,session:%d,service:%s]",
    +                (int)xact->id, (int)sbi_sess->id,
    +                xact->service_type ?
    +                    ogs_sbi_service_type_to_name(xact->service_type) :
    +                    "NULL");
    +        ogs_sbi_xact_remove(xact);
    +    }
    +}
    +
     static void session_remove(ogs_sbi_session_t *sbi_sess)
     {
         struct MHD_Connection *connection;
    @@ -171,6 +257,8 @@ static void session_remove(ogs_sbi_session_t *sbi_sess)
     
         ogs_list_remove(&server->session_list, sbi_sess);
     
    +    session_remove_xact_all(sbi_sess);
    +
         ogs_assert(sbi_sess->timer);
         ogs_timer_delete(sbi_sess->timer);
     
    
  • lib/sbi/nghttp2-server.c+95 0 modified
    @@ -43,6 +43,9 @@ static ogs_sbi_server_t *server_from_stream(ogs_sbi_stream_t *stream);
     static ogs_pool_id_t id_from_stream(ogs_sbi_stream_t *stream);
     static void *stream_find_by_id(ogs_pool_id_t id);
     
    +static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact);
    +static void xact_detach(ogs_sbi_xact_t *xact);
    +
     const ogs_sbi_server_actions_t ogs_nghttp2_server_actions = {
         server_init,
         server_final,
    @@ -58,6 +61,9 @@ const ogs_sbi_server_actions_t ogs_nghttp2_server_actions = {
     
         id_from_stream,
         stream_find_by_id,
    +
    +    xact_attach,
    +    xact_detach,
     };
     
     struct h2_settings {
    @@ -96,6 +102,15 @@ typedef struct ogs_sbi_stream_s {
         bool                    memory_overflow;
     
         ogs_sbi_session_t       *session;
    +
    +    /*
    +     * Outbound SBI transactions originated by this inbound stream.
    +     * Populated automatically when ogs_sbi_discover_and_send() sees
    +     * xact->assoc_stream_id pointing at this stream, and drained at
    +     * stream close so response timers are freed promptly rather
    +     * than lingering until the SBI client wait timeout.
    +     */
    +    ogs_list_t              xact_list;
     } ogs_sbi_stream_t;
     
     static void session_remove(ogs_sbi_session_t *sbi_sess);
    @@ -771,11 +786,89 @@ static ogs_sbi_stream_t *stream_add(
     
         stream->session = sbi_sess;
     
    +    ogs_list_init(&stream->xact_list);
    +
         ogs_list_add(&sbi_sess->stream_list, stream);
     
         return stream;
     }
     
    +static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact)
    +{
    +    ogs_assert(stream);
    +    ogs_assert(xact);
    +
    +    /*
    +     * Invariant: the server-layer wrapper (ogs_sbi_server_attach_xact)
    +     * already filtered out the already-attached case. Reaching the
    +     * backend with to_stream_list set is a programming error.
    +     */
    +    ogs_assert(!xact->to_stream_list);
    +
    +    /*
    +     * Cache the list head so xact_detach() can unlink in O(1)
    +     * without re-resolving the stream from assoc_stream_id.
    +     * to_stream_list serves as both attachment flag and cached head.
    +     */
    +    ogs_list_add(&stream->xact_list, &xact->to_stream_node);
    +    xact->to_stream_list = &stream->xact_list;
    +}
    +
    +static void xact_detach(ogs_sbi_xact_t *xact)
    +{
    +    ogs_assert(xact);
    +
    +    /*
    +     * Invariant: the server-layer wrapper (ogs_sbi_server_detach_xact)
    +     * already filtered out the not-attached case. Reaching the
    +     * backend with to_stream_list cleared is a programming error.
    +     */
    +    ogs_assert(xact->to_stream_list);
    +
    +    ogs_list_remove(xact->to_stream_list, &xact->to_stream_node);
    +
    +    xact->to_stream_list = NULL;
    +    xact->assoc_stream_id = OGS_INVALID_POOL_ID;
    +}
    +
    +/*
    + * Cancel every outbound SBI transaction that was triggered by this
    + * stream. Without this, ogs_sbi_xact_remove() and its response timer
    + * would only be reached when the upstream NF responds or the SBI
    + * client wait timer expires; a peer that rapidly resets streams
    + * while the upstream NF stalls would pile up those timers until the
    + * pool is exhausted (the crash signature in issues #4472 / #4473).
    + *
    + * ogs_sbi_xact_remove() invokes xact_detach() internally, which is
    + * what unlinks the node from this list. ogs_list_for_each_entry_safe
    + * caches the next pointer before the body runs, so detach-during-
    + * iteration is well-defined.
    + */
    +static void stream_remove_xact_all(ogs_sbi_stream_t *stream)
    +{
    +    ogs_sbi_xact_t *xact = NULL, *next_xact = NULL;
    +
    +    ogs_assert(stream);
    +
    +    ogs_list_for_each_entry_safe(
    +            &stream->xact_list, next_xact, xact, to_stream_node) {
    +        /*
    +         * Logged at error level so the cancellation shows up in
    +         * production traces alongside the upstream NF activity that
    +         * was abandoned. Useful for diagnosing #4472/#4473 style
    +         * patterns and any future regression where a peer resets
    +         * streams while upstream NFs are slow.
    +         */
    +        ogs_error("Canceling pending outbound SBI transaction "
    +                "on HTTP/2 stream close [xact:%d,stream:%d,service:%s]",
    +                (int)xact->id, stream->stream_id,
    +                xact->service_type ?
    +                    ogs_sbi_service_type_to_name(xact->service_type) :
    +                    "NULL");
    +        ogs_sbi_xact_remove(xact);
    +    }
    +}
    +
     static void stream_remove(ogs_sbi_stream_t *stream)
     {
         ogs_sbi_session_t *sbi_sess = NULL;
    @@ -786,6 +879,8 @@ static void stream_remove(ogs_sbi_stream_t *stream)
     
         ogs_list_remove(&sbi_sess->stream_list, stream);
     
    +    stream_remove_xact_all(stream);
    +
         ogs_assert(stream->request);
         ogs_sbi_request_free(stream->request);
     
    
  • lib/sbi/path.c+42 0 modified
    @@ -270,6 +270,48 @@ int ogs_sbi_discover_and_send(ogs_sbi_xact_t *xact)
         request = xact->request;
         ogs_assert(request);
     
    +    /*
    +     * If the NF associated this transaction with an inbound server
    +     * stream (by assigning xact->assoc_stream_id before calling us),
    +     * register the transaction on that stream's outbound xact list.
    +     *
    +     * When the inbound stream is later closed by the peer (HTTP/2
    +     * RST_STREAM, connection drop) before the upstream NF response
    +     * arrives, stream_remove() walks the list and cancels every
    +     * outstanding transaction. The response timer is returned to
    +     * the pool immediately rather than holding a slot until the
    +     * SBI client wait timeout, so a burst of short-lived inbound
    +     * requests cannot pile up enough pending timers to exhaust the
    +     * pool (issues #4472 and #4473).
    +     *
    +     * Transactions with no inbound stream (e.g. NRF discovery
    +     * initiated by the NF itself, status notifications) leave
    +     * assoc_stream_id at OGS_INVALID_POOL_ID and the helper is a
    +     * no-op (returns OGS_OK).
    +     *
    +     * If attach reports failure (originating stream already
    +     * closed), we DO NOT abort the upstream send and we DO NOT
    +     * propagate the failure to the NF caller: most NF wrappers
    +     * treat any non-OK return as a hard send failure and remove
    +     * the transaction, which would orphan an upstream request
    +     * already on the wire. The diagnostic context (xact id,
    +     * stream id, service type, file:line) is already emitted by
    +     * ogs_sbi_server_attach_xact() itself and by the ogs_error()
    +     * below, so the situation is observable without changing the
    +     * caller-visible return code. If a response later arrives,
    +     * the NF response handler will see no stream and drop it
    +     * through the existing "STREAM has already been removed"
    +     * path.
    +     */
    +    if (ogs_sbi_server_attach_xact(xact) != OGS_OK) {
    +        ogs_error("ogs_sbi_discover_and_send: attach failed, "
    +                "proceeding with upstream send "
    +                "[xact:%d,assoc_stream_id:%d,service:%s]",
    +                (int)xact->id, (int)xact->assoc_stream_id,
    +                ogs_sbi_service_type_to_name(service_type));
    +        /* fall through — upstream send must still happen */
    +    }
    +
         discovery_option = xact->discovery_option;
     
         /* SCP Availability */
    
  • lib/sbi/server.c+95 0 modified
    @@ -262,6 +262,101 @@ void *ogs_sbi_stream_find_by_id(ogs_pool_id_t id)
         return ogs_sbi_server_actions.stream_find_by_id(id);
     }
     
    +int ogs_sbi_server_attach_xact(ogs_sbi_xact_t *xact)
    +{
    +    ogs_sbi_stream_t *stream = NULL;
    +
    +    ogs_assert(xact);
    +
    +    /*
    +     * Idempotent. NFs may set xact->assoc_stream_id and reach the
    +     * SBI path multiple times for retries; attaching twice would
    +     * corrupt the list, so we skip when already attached. This is
    +     * not an error in itself — return OK so the caller continues.
    +     */
    +    if (xact->to_stream_list) {
    +        ogs_error("ogs_sbi_server_attach_xact: already attached "
    +                "[xact:%d,assoc_stream_id:%d,service:%s]",
    +                (int)xact->id, (int)xact->assoc_stream_id,
    +                xact->service_type ?
    +                    ogs_sbi_service_type_to_name(xact->service_type) :
    +                    "NULL");
    +        return OGS_OK;
    +    }
    +
    +    /*
    +     * No associated stream is normal for self-initiated transactions
    +     * (NRF discovery from the NF itself, status notifications, etc.).
    +     * Silent skip, OK.
    +     */
    +    if (xact->assoc_stream_id < OGS_MIN_POOL_ID ||
    +        xact->assoc_stream_id > OGS_MAX_POOL_ID)
    +        return OGS_OK;
    +
    +    /*
    +     * xact->assoc_stream_id is a "best-effort" linkage: it means
    +     * "if a response arrives and this stream is still alive,
    +     * deliver the response there." The stream is not required to
    +     * be alive at the moment the outbound transaction is sent.
    +     *
    +     * NFs are allowed to pass the same stream to discover_and_send()
    +     * even after that stream has already been used to answer the
    +     * original peer (and is therefore being closed). The common
    +     * case is a "respond then start follow-up" pattern, e.g. SMF
    +     * answering the AMF with HTTP 201 Created and then kicking off
    +     * Npcf_SMPolicyControl_Create.
    +     *
    +     * Two outcomes:
    +     *
    +     *   - Stream still alive: attach to its xact_list so the
    +     *     transaction is cancelled (and its response timer freed)
    +     *     if the peer later resets the stream. This is the
    +     *     #4472/#4473 protection path. Returns OGS_OK.
    +     *
    +     *   - Stream already gone: nothing to attach. Returns
    +     *     OGS_ERROR so the caller can log with its own context
    +     *     (NF name, UE/session id, etc.) and decide what to do.
    +     *     The caller may legitimately ignore the error and let
    +     *     the transaction proceed; the response (if any) will be
    +     *     dropped through the existing "STREAM has already been
    +     *     removed" path in the response handler.
    +     */
    +    stream = ogs_sbi_stream_find_by_id(xact->assoc_stream_id);
    +    if (!stream) {
    +        ogs_error("ogs_sbi_server_attach_xact: originating SBI stream "
    +                "already closed "
    +                "[xact:%d,assoc_stream_id:%d,service:%s]",
    +                (int)xact->id, (int)xact->assoc_stream_id,
    +                xact->service_type ?
    +                    ogs_sbi_service_type_to_name(xact->service_type) :
    +                    "NULL");
    +        return OGS_NOTFOUND;
    +    }
    +
    +    ogs_assert(ogs_sbi_server_actions.xact_attach);
    +    ogs_sbi_server_actions.xact_attach(stream, xact);
    +
    +    return OGS_OK;
    +}
    +
    +void ogs_sbi_server_detach_xact(ogs_sbi_xact_t *xact)
    +{
    +    ogs_assert(xact);
    +
    +    /*
    +     * Idempotent. No-op when not attached, including transactions
    +     * that never had an inbound stream (NRF discovery, status
    +     * notifications) where to_stream_list stays NULL. This is
    +     * invoked unconditionally from ogs_sbi_xact_remove(), so
    +     * streamless transactions reach here on every cleanup.
    +     */
    +    if (!xact->to_stream_list)
    +        return;
    +
    +    ogs_assert(ogs_sbi_server_actions.xact_detach);
    +    ogs_sbi_server_actions.xact_detach(xact);
    +}
    +
     static ogs_sbi_server_t *ogs_sbi_server_find_by_interface(
             ogs_sbi_server_t *current, const char *interface)
     {
    
  • lib/sbi/server.h+40 0 modified
    @@ -32,6 +32,7 @@ extern "C" {
     #include <openssl/err.h>
     
     typedef struct ogs_sbi_stream_s ogs_sbi_stream_t;
    +typedef struct ogs_sbi_xact_s ogs_sbi_xact_t;
     
     typedef struct ogs_sbi_server_s {
         ogs_socknode_t  node;
    @@ -71,6 +72,15 @@ typedef struct ogs_sbi_server_actions_s {
     
         ogs_pool_id_t (*id_from_stream)(ogs_sbi_stream_t *stream);
         void *(*stream_find_by_id)(ogs_pool_id_t id);
    +
    +    /*
    +     * Per-backend hooks linking an outbound SBI transaction into the
    +     * inbound server stream that triggered it. The list head lives
    +     * inside the backend-private stream/session struct, so each
    +     * backend (HTTP/2 vs MHD) provides its own attach/detach.
    +     */
    +    void (*xact_attach)(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact);
    +    void (*xact_detach)(ogs_sbi_xact_t *xact);
     } ogs_sbi_server_actions_t;
     
     void ogs_sbi_server_init(int num_of_session_pool, int num_of_stream_pool);
    @@ -107,6 +117,36 @@ ogs_sbi_server_t *ogs_sbi_server_from_stream(ogs_sbi_stream_t *stream);
     ogs_pool_id_t ogs_sbi_id_from_stream(ogs_sbi_stream_t *stream);
     void *ogs_sbi_stream_find_by_id(ogs_pool_id_t id);
     
    +/*
    + * Helpers used by lib/sbi to link an outbound SBI transaction into
    + * the inbound stream that triggered it. NFs do not call these
    + * directly: ogs_sbi_discover_and_send() attaches when
    + * xact->assoc_stream_id is set, and ogs_sbi_xact_remove() /
    + * stream close detach automatically.
    + *
    + * Both helpers are idempotent and consult xact->to_stream_list as
    + * the ground-truth attachment flag.
    + *
    + * ogs_sbi_server_attach_xact() returns OGS_OK when the transaction
    + * is either freshly attached, already attached, or has no stream
    + * to attach to (streamless self-initiated xact). It returns
    + * OGS_NOTFOUND when the originating stream id is valid but the
    + * stream itself is already gone.
    + *
    + * ogs_sbi_discover_and_send() does not propagate this OGS_NOTFOUND
    + * to the NF caller. Some NF procedures send the original HTTP response
    + * first and then continue with follow-up SBI transactions while still
    + * passing the original stream pointer for convenience. Treating this
    + * as a hard send failure would make those wrappers call their normal
    + * error path, which may try to send another response on a stream that
    + * has already been answered.
    + *
    + * Therefore, this condition is logged here, stream attachment is skipped,
    + * and the upstream send continues.
    + */
    +int ogs_sbi_server_attach_xact(ogs_sbi_xact_t *xact);
    +void ogs_sbi_server_detach_xact(ogs_sbi_xact_t *xact);
    +
     ogs_sbi_server_t *ogs_sbi_server_first(void);
     ogs_sbi_server_t *ogs_sbi_server_next(ogs_sbi_server_t *current);
     ogs_sbi_server_t *ogs_sbi_server_first_by_interface(const char *interface);
    
  • src/smf/nudm-handler.c+3 1 modified
    @@ -478,8 +478,10 @@ bool smf_nudm_sdm_handle_subscription(smf_sess_t *sess, ogs_sbi_stream_t *stream
          * If NOT Home-Routed Roaming,
          * Send HTTP_STATUS_CREATED(/nsmf-pdusession/v1/sm-context) to the AMF
          *********************************************************************/
    -    if (!HOME_ROUTED_ROAMING_IN_HSMF(sess))
    +    if (!HOME_ROUTED_ROAMING_IN_HSMF(sess)) {
             smf_sbi_send_sm_context_created_data(sess, stream);
    +        stream = NULL;
    +    }
     
         r = smf_sbi_discover_and_send(
                 OGS_SBI_SERVICE_TYPE_NPCF_SMPOLICYCONTROL, NULL,
    

Vulnerability mechanics

Root cause

"Timer pool exhaustion due to unbounded accumulation of pending outbound SBI transactions, leading to an assertion abort that crashes the AUSF process."

Attack vector

An authenticated remote attacker sends repeated bursts of `POST /nausf-auth/v1/ue-authentications` requests while the outbound `nudm-ueau` generate-auth-data requests to UDM are kept hanging (e.g., by a fake UDM that accepts but never responds). Each incoming auth request creates an SBI transaction with a response timer. When the client resets the HTTP/2 stream, AUSF frees the local stream and request objects immediately, but the outbound UDM transaction and its timer remain pending until the SBI client wait timeout. Repeating this quickly exhausts the finite timer pool [ref_id=1]. Once `ogs_timer_add()` fails, `ausf_sbi_discover_and_send()` returns `OGS_ERROR`, and the handler aborts via `ogs_assert(r != OGS_ERROR)`, crashing the AUSF process [ref_id=1].

Affected code

The vulnerability is in the AUSF component's `ausf_nausf_auth_handle_authenticate` handler (`src/ausf/nausf-handler.c:63`) which calls `ausf_sbi_discover_and_send()` (`src/ausf/sbi-path.c:97-103`). That function invokes `ogs_sbi_xact_add()` in `lib/sbi/context.c:2592-2600`, which allocates a response timer via `ogs_timer_add()` in `lib/core/ogs-timer.c:82-85`. The timer pool is sized globally at `lib/app/ogs-config.c:71-77,115-119` based on `MAX_NUM_OF_UE (1024) * POOL_NUM_PER_UE (16)`.

What the fix does

The patch [patch_id=3191424] replaces the `ogs_assert(r != OGS_ERROR)` hard abort in `ausf_nausf_auth_handle_authenticate` with a graceful error return, so that when timer pool exhaustion causes `ausf_sbi_discover_and_send()` to fail, the handler logs the error and returns an appropriate HTTP error response instead of crashing. This prevents the denial-of-service condition by ensuring the AUSF process remains running even when the timer pool is exhausted due to slow or unresponsive UDM responses.

Preconditions

  • networkAttacker must be able to send HTTP/2 POST requests to the AUSF /nausf-auth/v1/ue-authentications endpoint
  • configThe outbound nudm-ueau generate-auth-data requests must be kept pending (e.g., by a slow or unresponsive UDM)
  • inputAttacker must send repeated bursts of requests while resetting HTTP/2 streams to accumulate pending timers

Reproduction

1. Start a fake UDM that accepts generate-auth-data requests but never responds: `docker run --rm -d --name fake-ausf-udm-hang --network open5gs -v /home/ubuntu/open5gs_277/.audit_tmp:/srv node:24-alpine node /srv/ausf_fake_udm_hang.js` [ref_id=1]. 2. Restart AUSF and redirect nudm-ueau traffic to the fake UDM by overriding `udm.open5gs.org` in `/etc/hosts` [ref_id=1]. 3. Send repeated bursts of auth requests: `docker exec fake-ausf-udm-hang sh -lc 'for i in 1 2 3 4 5 6 7; do AUSF_STRESS_CONNECTIONS=8 AUSF_STRESS_REQUESTS_PER_CONNECTION=256 AUSF_STRESS_LAUNCH_ONLY_MS=1000 node /srv/ausf_auth_stress.js; done'` [ref_id=1]. 4. Observe AUSF crash with logs showing `ogs_timer_add() failed` followed by `Assertion 'r != OGS_ERROR' failed` [ref_id=1].

Generated on May 30, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.