kuzaxak created an issue (kamailio/kamailio#4799)

### Description

A Kamailio worker (the tm **slow-timer** process) crashes with `SIGSEGV` in 
`branch_builder()` during **DNS failover of a locally-generated UAC 
transaction** — i.e. a transaction created *without* an originating SIP message 
(dispatcher/keepalive `OPTIONS`, `uac` module request, etc.).

On final-response (FR) timeout the slow timer runs:
`final_response_handler()` → `add_uac_dns_fallback(t, msg=NULL, ...)` → (with 
`reparse_on_dns_failover` enabled) `add_uac_from_buf(t, request=NULL, ...)` → 
`print_uac_request_from_buf(t, i_req=NULL, ...)`.

`print_uac_request_from_buf()` dereferences `i_req` unconditionally 
(`src/modules/tm/t_fwd.c`):

```c
/* ... we calculate branch ... */
if(!t_calc_branch(t, branch, i_req->add_to_branch_s, 
&i_req->add_to_branch_len)) {
    ...
branch_str.s   = i_req->add_to_branch_s;
branch_str.len = i_req->add_to_branch_len;
```

`add_to_branch_s` is an array member, so with `i_req == NULL` it evaluates to 
the address `offsetof(sip_msg, add_to_branch_s)` (≈ `0x628` in our build) and 
`&i_req->add_to_branch_len` ≈ `0x664` (Δ = `0x3c`, the scratch-buffer size). 
Those bogus pointers become the output buffer passed to `t_calc_branch()` → 
`branch_builder()` (`src/core/msg_translator.c`), which writes the magic cookie 
/ `*len` through them → `SIGSEGV`. (`add_uac_from_buf()` *also* dereferences 
`request` directly, e.g. `request->first_line.u.request.method.len`, so the 
whole path is unusable when the originating message is NULL.)

Because a tm worker dies, the master aborts and the whole process restarts. 
Under traffic this becomes a crash loop (recurring every ~70–90 s in our 
deployment).

### Troubleshooting

#### Reproduction

Happens when all of the following hold:

- core: `use_dns_failover = on` (DNS cache on)
- tm: `reparse_on_dns_failover` enabled (the backtrace goes through 
`add_uac_from_buf`)
- a transaction generated locally **without** a SIP message (e.g. dispatcher 
keepalive `OPTIONS` via `t_uac`, or the `uac` module)
- that request times out with no reply, and its destination still has another 
DNS target (`dns_srv_handle_next()` returns true)

In our case it was triggered by dispatcher-style keepalive `OPTIONS` to a 
destination that had DNS-failover candidates.

#### Debugging Data

Symbolized backtrace (binary built with DWARF; site-specific IPs/hostnames 
sanitized to placeholders):

```
#0  branch_builder (hash_index=..., label=0, char_v=..., branch=1,
        branch_str=0x628 <error: Cannot access memory at address 0x628>, 
len=0x664)
        at src/core/msg_translator.c
        begin = 0x628 <error: Cannot access memory at address 0x628>
#1  t_calc_branch (t=..., b=1, branch=0x628, branch_len=0x664)
        at src/modules/tm/t_msgbuilder.c:1819
#2  print_uac_request_from_buf (t=..., i_req=0x0, branch=1, uri=..., len=..., 
dst=...,
        buf=0x... "OPTIONS sip: <TARGET_IP>:5060;transport=udp;conn_id=<uuid> 
SIP/2.0\r\n"
                  "Via: SIP/2.0/UDP <proxy-host>:5065;branch=z9hG4bK...", 
buf_len=493)
        at src/modules/tm/t_fwd.c:655
        branch_str = {s = 0x0, len = 0}
#3  add_uac_from_buf (t=..., request=0x0, uri=..., buf=0x... "OPTIONS sip: 
<TARGET_IP>:5060;...",
        buf_len=493) at src/modules/tm/t_fwd.c:900
#4  add_uac_dns_fallback (t=..., msg=0x0, old_uac=..., lock_replies=0)
        at src/modules/tm/t_fwd.c:1056
#5  final_response_handler (r_buf=..., t=...) at src/modules/tm/timer.c:455
#6  retr_buf_handler (ticks=..., tl=..., p=...) at src/modules/tm/timer.c:534
#7  slow_timer_main () at src/core/timer.c:1105
#8  main_loop () at src/main.c:1996
#9  main (...) at src/main.c:3407
```

Crash site (`src/modules/tm/t_fwd.c::print_uac_request_from_buf`, identical in 
6.0.6 / 6.0.7 / master):

```c
static char *print_uac_request_from_buf(struct cell *t, struct sip_msg *i_req,
        int branch, str *uri, unsigned int *len, struct dest_info *dst,
        char *buf, short buf_len)
{
    char *shbuf;
    str branch_str;
    ...
    shbuf = 0;
    /* ... we calculate branch ... */
    if(!t_calc_branch(
               t, branch, i_req->add_to_branch_s, &i_req->add_to_branch_len)) { 
 /* i_req == NULL here */
```

#### Log Messages

```
ALERT:    <core> [main.c:815]: handle_sigs(): child process NN exited by a 
signal 11
ALERT:    <core> [main.c:819]: handle_sigs(): core was generated
CRITICAL: <core> [core/pass_fd.c:284]: receive_fd(): EOF on 60
```

#### SIP Traffic

The locally-generated keepalive `OPTIONS` (the transaction that has no 
originating msg), sanitized. The crash is **independent of the URI content** — 
it is the NULL `i_req` on the failover path:

```
OPTIONS sip: <TARGET_IP>:5060;transport=udp;conn_id=<uuid> SIP/2.0
Via: SIP/2.0/UDP <proxy-host>:5065;branch=z9hG4bK...
To: <sip: <TARGET_IP>:5060;transport=udp;conn_id=<uuid>>
From: <sip:proxy@<proxy-domain>>;tag=...
CSeq: 10 OPTIONS
Call-ID: ...
Max-Forwards: 70
Content-Length: 0
User-Agent: ...
```

### Possible Solutions

Minimal fix — decline DNS failover when there is no originating message (both 
the `reparse_on_dns_failover` path via `add_uac_from_buf()` and the `add_uac()` 
path require it). Applies cleanly to current `master`:

```diff
--- a/src/modules/tm/t_fwd.c
+++ b/src/modules/tm/t_fwd.c
@@ -1076,6 +1076,18 @@
        int ret;
 
        ret = -1;
+
+       /* A locally generated UAC transaction (e.g. dispatcher/keepalive 
OPTIONS
+        * sent via t_uac, the uac module, ...) has no originating SIP message.
+        * The reparse/rebuild failover paths below dereference it 
unconditionally
+        * (add_uac_from_buf() / print_uac_request_from_buf() ->
+        * i_req->add_to_branch_s, and add_uac() -> msg->rcv), which segfaults 
when
+        * msg==NULL. Decline DNS failover for msg-less transactions. */
+       if(msg == NULL) {
+               LM_DBG("no originating request - skipping dns failover for a"
+                               " locally generated transaction\n");
+               return ret;
+       }
        if(cfg_get(core, core_cfg, use_dns_failover)
                        && !((t->flags & (T_DONT_FORK | T_DISABLE_FAILOVER))
                                        || uac_dont_fork(old_uac))
```

Recommended defense-in-depth (independent of the above): NULL-guard `i_req` at 
the top of `print_uac_request_from_buf()` and `request` in 
`add_uac_from_buf()`, returning an error instead of dereferencing. Happy to 
open a PR.

### Additional Information

* **Kamailio Version** - output of `kamailio -v`:

```
version: kamailio 6.0.6 (x86_64/linux)
flags: ... USE_DNS_CACHE, USE_DNS_FAILOVER, ...
```

The affected files `src/modules/tm/t_fwd.c` and `src/modules/tm/t_msgbuilder.c` 
are **byte-identical to upstream 6.0.6, 6.0.7 and current master** (verified) — 
the unguarded `i_req` dereference is present on `master` 
(`print_uac_request_from_buf`, ~line 714). **6.0.6, 6.0.7 and master are all 
affected.**

* **Operating System**: Amazon Linux 2023, kernel 6.12.x, x86_64.

Related but distinct (and never fixed): #2987 (locally-generated transaction + 
DNS failover, bad-URI symptom).


-- 
Reply to this email directly or view it on GitHub:
https://github.com/kamailio/kamailio/issues/4799
You are receiving this because you are subscribed to this thread.

Message ID: <kamailio/kamailio/issues/[email protected]>
_______________________________________________
Kamailio - Development Mailing List -- [email protected]
To unsubscribe send an email to [email protected]
Important: keep the mailing list in the recipients, do not reply only to the 
sender!

Reply via email to