Skip to content

heap-use-after-free in call::run() under a REGISTER + pause load scenario #875

Description

@joubertdj

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.

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