heap-use-after-free in call::run() under a REGISTER + pause load scenario
Summary
Running a simple UAC scenario that REGISTERs many AORs and then holds each
call open with a long <pause> (to keep the binding refreshed / wait for an
inbound request) reliably crashes with SIGSEGV. An AddressSanitizer build
shows a heap-use-after-free: a call object allocated in
call::add_call() is freed and then dereferenced again in call::run().
SUMMARY: AddressSanitizer: heap-use-after-free src/call.cpp:2309 in call::run()
previously allocated by thread T0 here:
#1 call::add_call(int, bool, sockaddr_storage*) src/call.cpp:962
#2 CallGenerationTask::run() src/call_generation_task.cpp:150
#3 traffic_thread src/sipp.cpp:633
(Line numbers are from my local checkout — see "Version" below; they are
shifted slightly from the v3.7.7 tag.)
The crash is rate-independent: it reproduces at -r 1 -rp 100
(10 calls/s) and equally at -r 1 -rp 2000 (1 call / 2 s), typically after
~150 calls have been generated. Without AddressSanitizer it surfaces a bit
later as a SIGSEGV inside free() (glibc get_meta) called from
task::setPaused() via call::executeMessage(), i.e. the freed call object
is touched again when it transitions into the paused-task wheel.
Version
sipp -v: SIPp v3.7.7-PCAP
- Built from source at commit:
be58e3e02ae27c39e1ec884e3d8738689732eb66
git describe --tags --always --dirty: v3.7.7-56-gbe58e3e
- Local modifications:
None
Environment
- OS: Ubuntu (x86_64)
- Build:
cmake . -DUSE_PCAP=1 (+ ASan build to capture the trace:
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address")
- Registrar under test: Asterisk 20.13.0 (chan_pjsip)
Command line
./sipp 192.168.66.12 -sf ./simpleclient_register.xml -inf ./simpleclient.csv \
-i 192.168.88.250 -p 6000 -t u1 -max_socket 500 -nd -l 400 -m 366 -r 1 -rp 100 -trace_err
Scenario (simpleclient_register.xml)
<?xml version="1.0" encoding="ISO-8859-1" ?>
<scenario name="simpleclient_register">
<label id="1"/>
<send retrans="500">
<![CDATA[
REGISTER sip:[remote_ip] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch]
Max-Forwards: 70
From: TALK_STANDARD <sip:[field0]@[remote_ip]>;tag=[call_number]
To: <sip:[field0]@[remote_ip]>
Call-ID: [call_id]
CSeq: [cseq] REGISTER
Contact: <sip:[field0]@[local_ip]:[local_port]>
Expires: 3600
User-Agent: SIPp/Linux
Content-Length: 0
]]>
</send>
<recv response="200" rtd="true"/>
<pause milliseconds="1800000"/>
<nop next="1"/>
</scenario>
Inject file is field0;field1 rows (AOR;port), SEQUENTIAL. ~370 rows.
The registrar challenges a small subset of AORs with 401 (unprovisioned),
so -nd "Continuing call on unexpected message" fires for those; the crash
also reproduces if those rows are removed.
Full AddressSanitizer output
=================================================================
==50023==ERROR: AddressSanitizer: heap-use-after-free
in call::run() at src/call.cpp:2309
-----------------------------------------------------------------
READ of size N at 0x5220001f5182 thread T0
[-- truncated in your paste --]
#0 call::run() src/call.cpp:2309 <-- the use-after-free access
... frames #1..#4 not captured ...
freed by thread T0 here:
[-- top frames truncated in your paste: operator delete + the
SIPp delete site live here; this is the block still needed --]
#5 0x56d6cb365ffa main src/sipp.cpp:2244
#6 0x7e4623e2a1c9 (libc.so.6+0x2a1c9)
#7 0x7e4623e2a28a __libc_start_main (libc.so.6+0x2a28a)
#8 0x56d6cb219334 _start (sipp+0xca334)
previously allocated by thread T0 here:
#0 0x7e4624cfe548 operator new(unsigned long) asan_new_delete.cpp:95
#1 0x56d6cb2329f4 call::add_call(int, bool, sockaddr_storage*)
src/call.cpp:962
#2 0x56d6cb27cb26 CallGenerationTask::run() src/call_generation_task.cpp:150
#3 0x56d6cb35aded traffic_thread src/sipp.cpp:633
#4 0x56d6cb365ffa main src/sipp.cpp:2244
#5 0x7e4623e2a1c9 (libc.so.6+0x2a1c9)
#6 0x7e4623e2a28a __libc_start_main (libc.so.6+0x2a28a)
#7 0x56d6cb219334 _start (sipp+0xca334)
-----------------------------------------------------------------
SUMMARY: AddressSanitizer: heap-use-after-free
src/call.cpp:2309 in call::run()
-----------------------------------------------------------------
Shadow bytes around the buggy address:
0x5220001f4f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5220001f4f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5220001f5000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5220001f5080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5220001f5100: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x5220001f5180: fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd
0x5220001f5200: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x5220001f5280: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x5220001f5300: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x5220001f5380: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x5220001f5400: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==50023==ABORTING
Notes
- The freed object is a
call; it is used again in call::run() after being
freed, with the paused-task / scheduler path involved (task::setPaused()).
- Appears tied to the task/timer-wheel scheduler used for paused calls.
- Workaround that avoids it: a register-and-exit scenario (no long
<pause>),
re-run periodically to refresh bindings — no call is ever held in the
paused-task wheel.
heap-use-after-free in call::run() under a REGISTER + pause load scenario
Summary
Running a simple UAC scenario that REGISTERs many AORs and then holds each
call open with a long
<pause>(to keep the binding refreshed / wait for aninbound request) reliably crashes with SIGSEGV. An AddressSanitizer build
shows a heap-use-after-free: a
callobject allocated incall::add_call()is freed and then dereferenced again incall::run().(Line numbers are from my local checkout — see "Version" below; they are
shifted slightly from the v3.7.7 tag.)
The crash is rate-independent: it reproduces at
-r 1 -rp 100(10 calls/s) and equally at
-r 1 -rp 2000(1 call / 2 s), typically after~150 calls have been generated. Without AddressSanitizer it surfaces a bit
later as a SIGSEGV inside
free()(glibcget_meta) called fromtask::setPaused()viacall::executeMessage(), i.e. the freed call objectis touched again when it transitions into the paused-task wheel.
Version
sipp -v:SIPp v3.7.7-PCAPbe58e3e02ae27c39e1ec884e3d8738689732eb66git describe --tags --always --dirty:v3.7.7-56-gbe58e3eNoneEnvironment
cmake . -DUSE_PCAP=1(+ ASan build to capture the trace:-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address")Command line
Scenario (simpleclient_register.xml)
Inject file is
field0;field1rows (AOR;port),SEQUENTIAL. ~370 rows.The registrar challenges a small subset of AORs with 401 (unprovisioned),
so
-nd"Continuing call on unexpected message" fires for those; the crashalso reproduces if those rows are removed.
Full AddressSanitizer output
Notes
call; it is used again incall::run()after beingfreed, with the paused-task / scheduler path involved (
task::setPaused()).<pause>),re-run periodically to refresh bindings — no call is ever held in the
paused-task wheel.