Skip to content

Conversation

@NormB
Copy link
Member

@NormB NormB commented Feb 12, 2026

Summary

Fixes a crash (fm_free: freeing dangling pkg pointer / qm_free_dbg: bad pointer (out of memory block\!) - aborting) when b2b_init_request() is called from an async resume route (e.g. after rest_post()). The worker process aborts with signal 6 (SIGABRT) and a core dump is generated, taking down the entire OpenSIPS instance.

Details

When an initial INVITE is processed with an async operation (e.g. async(rest_post(...), resume_route)), the TM module:

  1. Saves the original SIP request in shared memory (SHM) via t_newtran(), including all parsed headers and any lumps added before the async call (e.g. from record_route(), fix_nated_contact(), append_hf(), etc.)
  2. When the async operation completes, t_resume_async_request() in tm/async.c calls fake_req() (tm/t_msgbuilder.h:194) to build a faked SIP message for the resume route
  3. fake_req() does memcpy(faked_req, shm_msg, sizeof(struct sip_msg)) at line 202 — this copies the entire sip_msg struct, meaning the faked request's msg->headers, msg->add_rm, and msg->body_lumps pointers all point into SHM memory (they are the same SHM pointers from the transaction's stored request)
  4. fake_req() then PKG-duplicates only specific fields (new_uri, dst_uri, path_vec, set_global_address, set_global_port) and sets faked_req->msg_flags |= FL_TM_FAKE_REQ at line 211

When the resume route calls b2b_init_request(), the script-level handler b2bl_script_init_request() (b2b_logic/logic.c:3228) calls b2b_api.apply_lumps(msg) directly at line 3245 — before any B2B entities are created. This dispatches to b2b_apply_lumps() in b2b_entities/dlg.c, where the following crash sequence occurs:

  1. b2b_apply_lumps() checks if(\!msg->body_lumps && \!msg->add_rm) return 0; — if any module added lumps before the async call, these SHM lump pointers are non-NULL, so execution continues
  2. build_req_buf_from_sip_req() processes the lumps to build a new buffer
  3. free_sip_msg(msg) is called to free the old message structure
  4. Inside free_sip_msg() (parser/msg_parser.c:965), the call to free_hdr_field_lst(msg->headers) at line 980 walks the parsed header linked list and calls pkg_free() on each header node
  5. These headers are in SHM, not PKG — calling pkg_free() on SHM pointers triggers the memory allocator's safety check and aborts the process

TM's own free_faked_req() (tm/t_msgbuilder.h:345) is designed to handle this correctly — it carefully frees only the PKG-duplicated fields and never calls free_hdr_field_lst() on the headers. The b2b_entities module's b2b_apply_lumps() was missing this awareness.

The crash is deterministic: it occurs on every call to b2b_init_request() from an async resume route, provided any module added lumps to the request before the async call. Common lump-producing functions include record_route(), fix_nated_contact(), append_hf(), remove_hf(), and many others. In production configurations, lumps are almost always present.

Note that b2b_apply_lumps() is called from 6 sites — not only b2bl_script_init_request() (logic.c:3245) but also b2bl_script_bridge() (bridging.c:2290) and 4 internal call sites within dlg.c itself (lines 1103, 3306, 3373, 3755). The fix in the function body protects all of them.

Affected versions: The bug exists in all versions where b2b_apply_lumps() exists alongside TM async support. Confirmed on 3.6.2 (reporter) and 4.0.0-dev (our testing).

Solution

Add a guard at the top of b2b_apply_lumps() that checks the FL_TM_FAKE_REQ flag and returns early:

if (msg->msg_flags & FL_TM_FAKE_REQ)
    return 0;

This mirrors TM's own approach — FL_TM_FAKE_REQ (1<<18, defined in parser/msg_parser.h:121) is already set by fake_req() at tm/t_msgbuilder.h:211 precisely to mark faked requests. TM's free_faked_req() uses this same awareness to avoid freeing SHM headers.

Skipping lump application is safe for faked requests because:

  • The lumps on a faked request are SHM pointers inherited from the transaction copy — they cannot be safely modified or freed in PKG context
  • The b2b_entities module builds its own outgoing requests from scratch using the dialog information, not from the lump-applied buffer
  • b2b_apply_lumps() returning 0 simply means "no modifications were made", which all 6 callers already handle

Reproduction

Tested on OpenSIPS 4.0.0-dev (x86_64/linux, DBG_MALLOC enabled) with this minimal config:

loadmodule "proto_udp.so"
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "sipmsgops.so"
loadmodule "rest_client.so"
loadmodule "b2b_entities.so"
loadmodule "b2b_logic.so"

modparam("b2b_entities", "db_mode", 0)
modparam("b2b_logic", "db_mode", 0)

route {
    if (is_method("INVITE") && \!has_totag()) {
        record_route();   # adds lumps — saved to SHM by TM, inherited by faked req
        async(rest_post("http://127.0.0.1:8899/delay",
            "test=1", "application/x-www-form-urlencoded",
            $var(body), $var(ct), $var(rcode)), resume);
    }
}

route[resume] {
    $var(s) = b2b_server_new("server");
    $var(c) = b2b_client_new("client", "sip:target@127.0.0.1:5080");
    b2b_init_request("top_hiding");    # ← CRASH without fix
}

A simple Python HTTP server on port 8899 with a 100ms delay ensures the async machinery activates. A SIP INVITE sent via nc -u triggers the flow.

Test Result
Without fix, lumps present (record_route) CRITICAL: qm_free_dbg: bad pointer (out of memory block\!) - aborting, worker killed by signal 6, core dump generated, entire OpenSIPS instance terminates
With fix, lumps present (record_route) b2b_init_request completes successfully, B2B session established, process stable
Without fix, no lumps (no record_route) No crash — b2b_apply_lumps returns early at the \!msg->body_lumps && \!msg->add_rm check, confirming the lump-dependent trigger

Compatibility

No backward compatibility issues. The fix only adds an early-return guard for a specific message type (TM faked requests) that was previously crashing. No behavior change for normal (non-faked) SIP messages. The FL_TM_FAKE_REQ flag is defined in core headers (parser/msg_parser.h) and is available to all modules.

Closing issues

Closes #3796

When b2b_init_request() is called from an async resume route (e.g.
after rest_post), the SIP message is a TM faked request whose parsed
headers are memcpy'd SHM pointers from the transaction copy.
b2b_apply_lumps() calls free_sip_msg() which invokes pkg_free() on
those SHM pointers, triggering "bad pointer (out of memory block!) -
aborting" and crashing the worker process.

Guard b2b_apply_lumps() with a check for FL_TM_FAKE_REQ (already set
by TM's fake_req()) and return early, mirroring how TM's own
free_faked_req() avoids freeing the SHM header list.

Fixes: OpenSIPS#3796
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CRASH] Async resume route and B2BUA

1 participant