Skip to content

[CRASH] f_malloc: defragmentation leaves a stale pf back-pointer; SIGSEGV in fm_remove_free() #4234

Description

@Lt-Flash

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;
}
  1. opensips -f repro.cfg -a F_MALLOC -m 32 -M 16
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions