-
Notifications
You must be signed in to change notification settings - Fork 2
Security
chan_sofia was hardened deliberately, not incidentally. This page summarizes
the security model and the robustness posture, each grounded in the source. The
feature list is Features; the WebRTC trust model is detailed in
WebRTC.
-
Digest auth, MD5 + SHA-256 (RFC 7616). Selectable per deployment via
auth_algorithms, with anti-downgrade so the server accepts only an algorithm it actually offered (channels/chan_sofia.c:6713-6796,channels/sofia/include/chan_sofia_internal.h:91-93). -
Fresh, crypto-secure nonces. Each challenge uses a 128-bit nonce from
/dev/urandomwith a configurable TTL; an expired-nonce client is re-challenged (stale) rather than rejected (sofia_secure_nonce_gen(),channels/chan_sofia.c:6681). -
Constant-time digest compare. The response is compared with a
constant-time
memcmp(volatile accumulator + memory barrier) to deny a timing oracle (sofia_ct_memcmp,channels/chan_sofia.c:6664-6676). -
Fail-closed challenges. Under
alwaysauthreject, an unknown peer gets a fresh-nonce, timing-equalized 401 indistinguishable from a known-peer bad password;allowguest=noreturns 403 to an unknown caller rather than routing it (toll-fraud) (channels/chan_sofia.c:5471-5486).insecure=inviteis overridable byforce_invite_auth, which raises an auditableInsecureInviteBypassAMI event (channels/chan_sofia.c:5434-5455).
-
Three independent ACL chains per peer plus a global
localha: sourcepermit/deny(applied to the inbound INVITE before auth, with a timing-equalized 403 so it is not a peer-existence oracle),contactpermit/contactdeny(REGISTER Contact gate), anddirectmediapermit/directmediadeny(applied cross-leg before a direct-media re-INVITE).channels/chan_sofia.c:2868-2892,:5362-5388,:3143-3178. -
Multi-Contact REGISTER preflight. A REGISTER carrying several Contacts is
validated in full before any binding is applied, so a later fail-closed
Contact cannot leave earlier ones partially bound
(
channels/chan_sofia.c:6343-6347). -
Local anti-abuse SIP blacklist. Abusive source IPs (failed-auth probes,
malformed requests, unknown-peer REGISTERs) are counted and banned to blunt
brute-force and scanning (
channels/sofia/sofia_blacklist.c, checked atchannels/chan_sofia.c:10701). -
REGISTER expiry bounds. Minimum/maximum lifetimes are enforced
(
423 Interval Too Brief+Min-Expires), which blunts registration-flood abuse (channels/chan_sofia.c:2325).
-
TLS hardening knobs on the TLS listener:
tlsverify(verify server cert),tlsverifyclient(mutual TLS / client cert),tls_min_version(1.0–1.3 floor),tls_ciphers,tls_verify_depth, and a verify-date check. This closes the accept-any-cert MITM hole sofia-sip leaves open by default (channels/chan_sofia.c:11647-11681). -
Modern TLS floor by default.
tls_min_versiondefaults to 1.2, so the TLS/WSS listener negotiates TLS 1.2 or 1.3 out of the box. The former 1.3-only default rejected otherwise-valid endpoints that cap at TLS 1.2; the 1.2 floor restores that interoperability without admitting the deprecated TLS 1.0/1.1, and a deployment that requires 1.3-only can still raise the floor explicitly (channels/chan_sofia.c:11647-11681). -
WSS builds its own SSL context and auto-aliases the WSS cert/ca-bundle
files from the standard TLS material (
channels/chan_sofia.c:11611-11632).
-
SRTP/SDES, validated and staged.
a=cryptois validated in full (no truncation) and staged, then committed only past every reject gate, so a bada=cryptonever silently downgrades a live encrypted call to plaintext (channels/sofia/sdp_crypto.c, commit point inchannels/sofia/sofia_sdp.c). -
Deferred SRTP keying. Crypto is validated/staged with
defer=1but the liveadd_srtp_policyis withheld until the single commit block runs after the gates — so a re-INVITE that would be rejected can never have touched the live keys (channels/sofia/sofia_sdp.c:780-793,:1882-1918). -
DTLS-SRTP trust anchored on the fingerprint, fail-closed (RFC 8827/5763).
The self-signed WebRTC cert cannot chain-verify, so the SDP
a=fingerprintis the sole trust anchor: on handshake completion the engine demands a peer cert, requires a stored remote fingerprint, and does a hash-matchedX509_digest+memcmp; any absence or mismatch tears the call down as a possible MITM (res/res_rtp_gabpbx.c:1097-1120). When GABPBX is the offerer, a DTLS record arriving before the answer'sa=fingerprintis installed is deferred (DTLS retransmits) rather than completing against no anchor — the verification still happens, never bypassed, once the fingerprint is in place (RFC 5763/8842). -
Anti-downgrade SDP gating (RFC 5763/8842). A SAVPF/DTLS offer to a
non-WebRTC peer is rejected rather than answered as plain RTP/AVP, a DTLS video
offer in a non-WebRTC-audio session is rejected, and on the offerer leg a plain
RTP/AVP answer to a DTLS-SRTP offer is rejected — so media never connects
silently insecure (
channels/sofia/sofia_sdp.c:1840-1866). -
Encryption never bridged. Direct media is force-disabled on any SRTP/DTLS
leg, so keys are never relayed to a non-secure peer
(
channels/chan_sofia.c:3129-3134). -
RFC 7983 demux with a flood guard on the muxed WebRTC socket, dormant
unless DTLS is active (
res/res_rtp_gabpbx.c:1732-1786). -
RED (RFC 2198) redundant-text sender, memory-safety hardened. The T.140
redundant-text generator bounds-checks its per-generation block arrays, clamps
or rejects any block larger than the RFC 2198 1023-byte length limit, and caps
the redundancy buffer, so a malformed or oversized redundant payload can never
overrun the send buffer (
res/res_rtp_gabpbx.c).
The driver does not leave its concurrency model implicit.
-
One canonical lock order, documented as an authoritative invariant block
at the top of the file:
channel-lock -> pvt->lock -> peer-family, never inverted; every inline lock site cites that one block (channels/chan_sofia.c:70-113). -
Single-owner thread model. One
sofia_thread(the su_root event loop) owns all mutable peer/dialog signaling state, runs everynua_*callback andsofia_process_*handler, and runs config reload (dispatched onto it). Everyone else (PBX/bridge callbacks, dialplan, CLI/AMI, sched, qualify) only reads, under lock. A race exists only if a party runs offsofia_thread— which makes every concurrency question a per-party thread check (channels/chan_sofia.c:74-83). -
Snapshot-under-lock off the Sofia thread. Any off-thread access to
freeable peer/owner data must snapshot under lock first, because
ast_string_field_setfrees the old pool on growth — a lock-free off-thread read is a use-after-free. The codified idiom (copy into a local underpeer->lock, release, then use) is atchannels/chan_sofia.c:98-107. -
Reload use-after-free fixes. A
sip reloadfrees and rebuilds peer stringfields/ACLs while readers touch them; the reload writer holdspeer->lockacross the whole reset+repopulate span so readers never see a torn peer, and off-thread readers were converted to snapshot-under-lock. The reload posts work ontosofia_threadand blocks on a condvar with a 30 s deadline, so there is exactly one consumer of config state during a reload (channels/chan_sofia.c:14078-14112). -
Dialog teardown-race mitigation. An in-dialog event carries the dialog as
the handle magic, but a concurrent hangup can free it; the dispatcher
re-validates the hmagic and pins a +1 ref for the whole event, and the
destructor unbinds the handle (
nua_handle_bind(nh, NULL)) before the async destroy, so a late event readshmagic == NULLand short-circuits instead of dereferencing freed memory (channels/chan_sofia.c:108-112,:1643-1656). -
Foreign-thread
nua_*marshalling. Because Sofia-SIP'snua_*/su_*APIs are not safe from arbitrary threads, every off-thread operation that must touch the stack is marshalled ontosofia_threadviasofia_dispatch_to_root_thread(channels/chan_sofia.c:14045-14076). -
Validate-then-commit SDP. A re-INVITE that fails any gate leaves the
established call fully unchanged per RFC 3261 §14: all mutations are staged
into locals or snapshotted and committed only past the last reject gate; the
reject label restores snapshots and destroys any was-new instances
(
channels/sofia/sofia_sdp.c). -
Bounded SDP build buffers. The generator uses a checked appender with a
sticky overflow flag, so a generated offer/answer is never silently truncated
into an invalid SDP; on overflow it returns NULL and callers handle it
(
channels/sofia/sofia_sdp.c:59-68).
The hardening is the product of a structured, repeated adversarial review — multiple robustness rounds plus a leak audit and a reload-UAF worklist, each run as independent read-only passes (a reject-path lens and a memory-safety/RFC lens) cross-reviewed to consensus, with every candidate bug grounded at file:line and RFC section. The review repeatedly caught defects in the first-draft fixes themselves.
A repeatable concurrency stress harness is the standing regression net: it
floods the listener with INVITE + re-INVITE(hold) + BYE while hammering
sip reload in a loop under DEBUG_THREADS, then asserts PASS/FAIL by PID
stability, absence of deadlock/assert/segv in the log, and a clean
core show locks. It is run after any change touching locks, reload,
dialog/peer lifetime, or event dispatch.
SIP — chan_sofia
Subsystems
Realtime & data
Operations