OpenSIPS version you are running
version: opensips 4.1.0-dev (x86_64/linux)
flags: STATS: On, DISABLE_NAGLE, USE_MCAST, SHM_MMAP, PKG_MALLOC, Q_MALLOC, F_MALLOC, HP_MALLOC, F_PARALLEL_MALLOC, DBG_MALLOC, FAST_LOCK-ADAPTIVE_WAIT
ADAPTIVE_WAIT_LOOPS=1024, MAX_RECV_BUFFER_SIZE 262144, MAX_LISTEN 16, MAX_URI_SIZE 1024, BUF_SIZE 65535
poll method support: poll, epoll, sigio_rt, select.
main.c compiled on 11:11:52 Sep 2 2026 with cc 9
Built from master at 32d67aed92.
The bug
f_malloc fragments carry pf, a pointer to the physically preceding
fragment, so a merge must re-point the following fragment at the survivor.
fm_free() does this; fm_malloc()'s defragmentation loop does not:
/* fm_free() - correct */
f->size += neigh->size + FRAG_OVERHEAD;
FRAG_NEXT(neigh)->pf = f;
/* fm_malloc() defragmentation - the fixup is missing */
fm_remove_free(fm, n);
frag->size += n->size + FRAG_OVERHEAD;
After the merge, the fragment following the pair still points at n, which is
now inside frag's payload. When it is freed, fm_free() reads that stale
pf as FRAG_PREV, frag_is_free() (which is just ((_f)->prev)) tests
payload bytes, and fm_remove_free() executes *pf = n->u.nxt_free; through
whatever it found:
#0 fm_remove_free (n=0x7fcc9a112548, fm=...) at mem/f_malloc.c:176
#1 fm_free (...) at mem/f_malloc_dyn.h:264
#2 _shm_free_bulk (file="h_table.c", line=204) at ../../mem/shm_mem.h:598
#3 free_cell (dead_cell=0x7fcc9a112708) at h_table.c:204
#4 delete_cell (...) at timer.c:243
#5 wait_handler (...) at timer.c:484
A general protection fault, not a NULL dereference — consistent with prev
read out of payload. (I have the core dump and can provide it.)
Fix
--- a/mem/f_malloc_dyn.h
+++ b/mem/f_malloc_dyn.h
@@ -144,6 +144,7 @@ void *fm_malloc(struct fm_block *fm, unsigned long size,
{
fm_remove_free(fm, n);
frag->size += n->size + FRAG_OVERHEAD;
+ FRAG_NEXT(frag)->pf = frag;
fm_malloc_init() sets up fm->last_frag as a real header, so no bounds
check is needed — as in fm_free().
To Reproduce
A transaction per request that is suspended, against a pool small enough to
defragment constantly. Suspension is what exposes it: a synchronous route frees
its cell inline, before the layout can go wrong, while async() leaves it for
tm's timer — hence wait_handler in the trace. sleep is used only because it
is a stock async function.
log_level=2
stderror_enabled=yes
syslog_enabled=yes
udp_workers=4
socket=udp:YOUR_IP:5060
mpath="/path/to/modules/"
loadmodule "proto_udp.so"
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "maxfwd.so"
loadmodule "sipmsgops.so"
loadmodule "cfgutils.so"
modparam("tm", "fr_timeout", 5)
modparam("tm", "fr_inv_timeout", 30)
route {
if (!mf_process_maxfwd_header(10)) {
send_reply(483, "Too Many Hops");
exit;
}
if (is_method("INVITE")) {
if (!t_newtran()) {
send_reply(500, "no transaction");
exit;
}
$avp(one) = "a moderately long profile value so the fragment is not tiny 01234567890123456789";
$avp(two) = $ci;
$avp(three) = "short";
async(sleep(1), resume_route);
exit;
}
if (is_method("ACK|BYE|CANCEL")) {
send_reply(200, "OK");
exit;
}
send_reply(405, "Method Not Allowed");
exit;
}
route[resume_route] {
t_reply(200, "OK");
exit;
}
opensips -f repro.cfg -a F_MALLOC -m 32 -M 16
- INVITEs at ~2000 cps for ~2 min (several SIPp processes — one saturates
near 1000-1250 cps). The log fills with not enough contiguous free shm memory ... attempting defragmentation.
- A worker faults and the process exits, after ~12,000 defragmentation passes.
Relevant System Logs
WARNING:core:fm_malloc: not enough contiguous free shm memory (949616 bytes
left, need 5704), attempting defragmentation...
ERROR:tm:sip_msg_cloner: no more share memory
ERROR:tm:new_t: out of mem
CRITICAL:core:sig_usr: segfault in process pid: 136109, id: 3
kernel: traps: opensips[136109] general protection fault
Verification
| build |
trials |
crashed |
master |
4 |
4 |
master + the patch above |
4 |
0 (~35,000 defrag passes each) |
Only f_malloc is affected: it is the only allocator holding a pointer to the
physically preceding fragment. q_malloc has no defragmentation pass,
hp_malloc coalesces forward only, and f_parallel_malloc has no pf.
OS/environment information
- Operating System: Ubuntu 20.04.6 LTS (x86_64), kernel 5.4.0-216-generic
- OpenSIPS installation: git (
master, 32d67aed92), built from source, gcc 9.4.0
- Other: the build carries
-DDBG_MALLOC, so the allocator must be named
explicitly (-a F_MALLOC) or the runtime default is Q_MALLOC_DBG.
OpenSIPS version you are running
Built from
masterat32d67aed92.The bug
f_mallocfragments carrypf, a pointer to the physically precedingfragment, so a merge must re-point the following fragment at the survivor.
fm_free()does this;fm_malloc()'s defragmentation loop does not:After the merge, the fragment following the pair still points at
n, which isnow inside
frag's payload. When it is freed,fm_free()reads that stalepfasFRAG_PREV,frag_is_free()(which is just((_f)->prev)) testspayload bytes, and
fm_remove_free()executes*pf = n->u.nxt_free;throughwhatever it found:
A general protection fault, not a NULL dereference — consistent with
prevread out of payload. (I have the core dump and can provide it.)
Fix
fm_malloc_init()sets upfm->last_fragas a real header, so no boundscheck is needed — as in
fm_free().To Reproduce
A transaction per request that is suspended, against a pool small enough to
defragment constantly. Suspension is what exposes it: a synchronous route frees
its cell inline, before the layout can go wrong, while
async()leaves it fortm's timer — hence
wait_handlerin the trace.sleepis used only because itis a stock async function.
opensips -f repro.cfg -a F_MALLOC -m 32 -M 16near 1000-1250 cps). The log fills with
not enough contiguous free shm memory ... attempting defragmentation.Relevant System Logs
Verification
mastermaster+ the patch aboveOnly
f_mallocis affected: it is the only allocator holding a pointer to thephysically preceding fragment.
q_mallochas no defragmentation pass,hp_malloccoalesces forward only, andf_parallel_mallochas nopf.OS/environment information
master,32d67aed92), built from source, gcc 9.4.0-DDBG_MALLOC, so the allocator must be namedexplicitly (
-a F_MALLOC) or the runtime default isQ_MALLOC_DBG.