Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pjmedia/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ if(BUILD_TESTING)
src/test/tone_detector_test.c
src/test/sdp_neg_test.c
src/test/sdp_attr_test.c
src/test/transport_srtp_test.c
)

target_link_libraries(pjmedia-test
Expand Down
2 changes: 1 addition & 1 deletion pjmedia/build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export PJMEDIA_TEST_SRCDIR = ../src/test
export PJMEDIA_TEST_OBJS += codec_vectors.o conf_test.o jbuf_test.o main.o mips_test.o \
vid_codec_test.o vid_dev_test.o vid_port_test.o \
rtp_test.o test.o tone_detector_test.o
export PJMEDIA_TEST_OBJS += sdp_neg_test.o sdp_attr_test.o
export PJMEDIA_TEST_OBJS += sdp_neg_test.o sdp_attr_test.o transport_srtp_test.o
export PJMEDIA_TEST_CFLAGS += $(_CFLAGS)
export PJMEDIA_TEST_CXXFLAGS += $(_CXXFLAGS)
export PJMEDIA_TEST_LDFLAGS += $(PJMEDIA_CODEC_LDLIB) \
Expand Down
4 changes: 4 additions & 0 deletions pjmedia/build/pjmedia_test.vcproj
Original file line number Diff line number Diff line change
Expand Up @@ -4938,6 +4938,10 @@
/>
</FileConfiguration>
</File>
<File
RelativePath="..\src\test\transport_srtp_test.c"
>
</File>
<File
RelativePath="..\src\test\test.c"
>
Expand Down
1 change: 1 addition & 0 deletions pjmedia/build/pjmedia_test.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@
</ClCompile>
<ClCompile Include="..\src\test\sdp_attr_test.c" />
<ClCompile Include="..\src\test\sdp_neg_test.c" />
<ClCompile Include="..\src\test\transport_srtp_test.c" />
<ClCompile Include="..\src\test\session_test.c">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-Dynamic|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-Dynamic|x64'">true</ExcludedFromBuild>
Expand Down
3 changes: 3 additions & 0 deletions pjmedia/build/pjmedia_test.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
<ClCompile Include="..\src\test\sdp_neg_test.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\test\transport_srtp_test.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\test\sdptest.c">
<Filter>Source Files</Filter>
</ClCompile>
Expand Down
7 changes: 6 additions & 1 deletion pjmedia/include/pjmedia/transport_srtp.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ typedef struct pjmedia_srtp_setting
* RTP sequence rollover counter initialization value for incoming
* direction. This is useful to maintain ROC after media transport
* recreation such as in IP change scenario.
* Note that once a packet has been authenticated, the transport
* updates this to the ROC of that packet, so callers of
* #pjmedia_transport_srtp_get_setting() may see it changed.
*/
pjmedia_srtp_roc rx_roc;

Expand All @@ -258,7 +261,9 @@ typedef struct pjmedia_srtp_setting
* ROC but for some reason, they don't. Thus, when we encounter
* SRTP packet unprotect failure during probation, we will retry to
* unprotect with this ROC value as well.
* Set prev_rx_roc.ssrc to 0 to disable this feature.
* Set prev_rx_roc.ssrc to 0 to disable this feature. The transport
* clears prev_rx_roc.ssrc once a packet of rx_roc.ssrc has been
* authenticated, closing the retry window.
*/
pjmedia_srtp_roc prev_rx_roc;

Expand Down
78 changes: 68 additions & 10 deletions pjmedia/src/pjmedia/transport_srtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ typedef struct transport_srtp

pj_uint32_t tx_ssrc;

/* ROC retry: prev_rx_roc.roc is the candidate currently applied. */
pj_bool_t rx_roc_retry_alt;

} transport_srtp;


Expand Down Expand Up @@ -917,7 +920,11 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_get_setting(
pjmedia_srtp_setting *opt)
{
transport_srtp *srtp = (transport_srtp*) tp;

pj_lock_acquire(srtp->mutex);
*opt = srtp->setting;
pj_lock_release(srtp->mutex);

return PJ_SUCCESS;
}

Expand All @@ -929,7 +936,11 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_modify_setting(
const pjmedia_srtp_setting *opt)
{
transport_srtp *srtp = (transport_srtp*) tp;

pj_lock_acquire(srtp->mutex);
srtp->setting = *opt;
pj_lock_release(srtp->mutex);

return PJ_SUCCESS;
}

Expand All @@ -943,6 +954,7 @@ static pj_status_t create_srtp_ctx(transport_srtp *srtp,
{
srtp_policy_t tx_;
srtp_policy_t rx_;
srtp_policy_t rx_tmpl;
srtp_err_status_t err;
int cr_tx_idx = 0;
int au_tx_idx = 0;
Expand Down Expand Up @@ -1054,8 +1066,10 @@ static pj_status_t create_srtp_ctx(transport_srtp *srtp,
else
rx_.rtp.sec_serv = sec_serv_none;
rx_.key = (uint8_t*)ctx->rx_key;
if (setting->rx_roc.roc != 0 &&
setting->rx_roc.ssrc != 0)
if (setting->rx_roc.ssrc != 0 &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With ssrc_specific in the remote-change case there's no longer an ssrc_any_inbound template in the RX context. As you traced, a new SSRC from the remote within probation still works via the no_ctx restart. But PJMEDIA_SRTP_CHECK_SSRC_ON_RESTART only runs while probation_cnt > 0, so an SSRC change after probation (e.g. an SBC switching media source with the same SDES keys) now gets no_ctx on every packet, and incoming audio dies. Before this commit, libsrtp would clone a stream from the template.

Note that the rx_roc.roc != 0 case already had this exposure before this PR; this commit extends it to the remote-change case. Could you check whether giving srtp_create() a policy list works here, i.e. the ssrc_specific stream plus an ssrc_any_inbound template via rx_.next? That would keep the stream available for srtp_set_stream_roc() and still accept new SSRCs. I haven't verified how libsrtp handles that combination.

(setting->rx_roc.roc != 0 ||
(setting->prev_rx_roc.ssrc == setting->rx_roc.ssrc &&
setting->prev_rx_roc.roc != 0)))
{
rx_.ssrc.type = ssrc_specific;
rx_.ssrc.value = setting->rx_roc.ssrc;
Expand All @@ -1072,6 +1086,16 @@ static pj_status_t create_srtp_ctx(transport_srtp *srtp,
rx_.rtcp = rx_.rtp;
rx_.rtcp.auth_tag_len = crypto_suites[au_rx_idx].srtcp_auth_tag_len;
rx_.next = NULL;
if (rx_.ssrc.type == ssrc_specific) {
/* Keep a wildcard template alongside the specific stream, so
* a remote SSRC change after probation is still accepted.
*/
rx_tmpl = rx_;
rx_tmpl.ssrc.type = ssrc_any_inbound;
rx_tmpl.ssrc.value = 0;
rx_tmpl.next = NULL;
rx_.next = &rx_tmpl;
}
err = srtp_create(&ctx->srtp_rx_ctx, &rx_);
if (err != srtp_err_status_ok) {
srtp_dealloc(ctx->srtp_tx_ctx);
Expand All @@ -1089,6 +1113,8 @@ static pj_status_t create_srtp_ctx(transport_srtp *srtp,
setting->rx_roc.ssrc, setting->rx_roc.roc,
(err == srtp_err_status_ok)? "succeeded": "failed"));
}
if (ctx == &srtp->srtp_ctx)
srtp->rx_roc_retry_alt = PJ_FALSE;
ctx->rx_policy = *rx;
pj_strset(&ctx->rx_policy.key, ctx->rx_key, rx->key.slen);
ctx->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name);
Expand Down Expand Up @@ -1653,6 +1679,13 @@ static void srtp_rtp_cb(pjmedia_tp_cb_param *param)
return;
}

/* Runt packets cannot hold an RTP header. */
if (size < (pj_ssize_t)sizeof(pjmedia_rtp_hdr)) {
PJ_LOG(5, (srtp->pool->obj_name, "Runt RTP packet, size=%ld",
(long)size));
return;
}

/* Give the packet to keying first by invoking its send_rtp() op.
* Yes, the usage of send_rtp() is rather hacky, but it is convenient
* as the signature suits the purpose and it is ready to use
Expand Down Expand Up @@ -1751,6 +1784,7 @@ static void srtp_rtp_cb(pjmedia_tp_cb_param *param)

#if PJMEDIA_SRTP_CHECK_ROC_ON_RESTART
if (srtp->probation_cnt > 0 && err == srtp_err_status_auth_fail &&
rx_ssrc == srtp->setting.rx_roc.ssrc &&
srtp->setting.prev_rx_roc.ssrc != 0 &&
srtp->setting.prev_rx_roc.ssrc == srtp->setting.rx_roc.ssrc &&
srtp->setting.prev_rx_roc.roc != srtp->setting.rx_roc.roc)
Expand Down Expand Up @@ -1790,28 +1824,40 @@ static void srtp_rtp_cb(pjmedia_tp_cb_param *param)
}

} else if (need_retry) {
unsigned roc, new_roc;
unsigned old_roc, new_roc;
srtp_err_status_t status;

srtp_get_stream_roc(srtp->srtp_ctx.srtp_rx_ctx,
srtp->setting.rx_roc.ssrc, &roc);
new_roc = (roc == srtp->setting.rx_roc.roc?
srtp->setting.prev_rx_roc.roc: srtp->setting.rx_roc.roc);
/* Try the alternative candidate, the setting is only updated
* after a packet authenticates.
*/
old_roc = (srtp->rx_roc_retry_alt? srtp->setting.prev_rx_roc.roc :
srtp->setting.rx_roc.roc);
new_roc = (srtp->rx_roc_retry_alt? srtp->setting.rx_roc.roc :
srtp->setting.prev_rx_roc.roc);
status = srtp_set_stream_roc(srtp->srtp_ctx.srtp_rx_ctx,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry never runs in the remote-change case (rx_roc.roc=0, prev_rx_roc.roc=N).

pjsua_media.c (~3993-4001) sets rx_roc.roc=0 and prev_rx_roc=<old ssrc, N> on a remote change. create_srtp_ctx() then sees rx_roc.roc == 0, builds the RX policy with ssrc_any_inbound and skips srtp_set_stream_roc(). If the remote didn't reset its ROC, unprotecting with the template fails with auth_fail. We then call srtp_set_stream_roc(rx_ctx, rx_roc.ssrc, N), but srtp_get_stream() only searches stream_list, not the template, so the call returns srtp_err_status_bad_param. The if (status == srtp_err_status_ok) check skips the retry with no log, and this repeats for the whole probation window.

So the two candidates only alternate in the local-change case. At minimum, please log this failure. Ideally, make the remote-change case work too, e.g. by creating the stream so the ROC can be set on it.

srtp->setting.rx_roc.ssrc, new_roc);
if (status == srtp_err_status_ok) {
srtp->rx_roc_retry_alt = !srtp->rx_roc_retry_alt;
PJ_LOG(4, (srtp->pool->obj_name,
"Retrying to unprotect SRTP from ROC %d to new ROC %d",
roc, new_roc));
old_roc, new_roc));
err = srtp_unprotect(srtp->srtp_ctx.srtp_rx_ctx, (pj_uint8_t*)pkt,
&len);
} else {
/* Cannot set the ROC, stop retrying. */
srtp->setting.prev_rx_roc.ssrc = 0;
PJ_LOG(4, (srtp->pool->obj_name,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If srtp_set_stream_roc() fails, rx_roc_retry stays PJ_TRUE. Every auth-failed packet in probation (~100, PROBATION_CNT_INIT) then retries an operation that can't succeed, and each one logs this at level 4. Please set srtp->rx_roc_retry = PJ_FALSE; in this branch so it logs once and stops.

"Failed to set RX ROC %d for retry, err=%s",
new_roc, get_libsrtp_errstr(status)));
}
}

if (err != srtp_err_status_ok) {
PJ_LOG(5,(srtp->pool->obj_name,
"Failed to unprotect SRTP, pkt size=%ld, SSRC=%u, err=%s",
size, rx_ssrc, get_libsrtp_errstr(err)));
"Failed to unprotect SRTP, pkt size=%ld, SSRC=%u, "
"seq=%u, err=%s",
size, rx_ssrc, ntohs(((pjmedia_rtp_hdr*)pkt)->seq),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this reads the RTP header without checking the packet length. A runt datagram (for example 1-3 bytes) gets here with bad_param and reads seq past size, which is an out-of-bounds read under ASan. The rx_ssrc read at line 1733 already has the same problem. One size >= sizeof(pjmedia_rtp_hdr) check early on would cover both.

get_libsrtp_errstr(err)));
} else {
cb = srtp->rtp_cb;
cb2 = srtp->rtp_cb2;
Expand All @@ -1822,6 +1868,18 @@ static void srtp_rtp_cb(pjmedia_tp_cb_param *param)

/* Save SSRC after successful SRTP unprotect */
srtp->rx_ssrc = rx_ssrc;

/* Settle the ROC retry on the SSRC it belongs to: write back
* the winning ROC and close the retry window.
*/
if (srtp->setting.prev_rx_roc.ssrc != 0 &&
rx_ssrc == srtp->setting.rx_roc.ssrc)
{
if (srtp->rx_roc_retry_alt)
srtp->setting.rx_roc.roc = srtp->setting.prev_rx_roc.roc;
srtp->setting.prev_rx_roc.ssrc = 0;
srtp->rx_roc_retry_alt = PJ_FALSE;
}
}

pj_lock_release(srtp->mutex);
Expand Down
4 changes: 4 additions & 0 deletions pjmedia/src/test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ int test_main(int argc, char *argv[])
#if HAS_TONE_DETECTOR_TEST
UT_ADD_TEST(&test_app.ut_app, tone_detector_test, 0);
#endif
#if HAS_TRANSPORT_SRTP_TEST
/* Creates/destroys a local pjmedia_endpt (see codec_test_vectors). */
UT_ADD_TEST(&test_app.ut_app, transport_srtp_test, PJ_TEST_EXCLUSIVE);
#endif
#if HAS_CODEC_VECTOR_TEST
/* Run in exclusive mode: creates/destroys a local pjmedia_endpt which
* sets/clears the global def_codec_mgr. If sdp_neg_test runs
Expand Down
3 changes: 3 additions & 0 deletions pjmedia/src/test/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
#define HAS_MIPS_TEST WITH_BENCHMARK
#define HAS_CODEC_VECTOR_TEST 1
#define HAS_TONE_DETECTOR_TEST 1
#define HAS_TRANSPORT_SRTP_TEST (PJMEDIA_HAS_SRTP && \
PJMEDIA_SRTP_CHECK_ROC_ON_RESTART)

int session_test(void);
int rtp_test(void);
Expand All @@ -53,6 +55,7 @@ int vid_codec_test(void);
int vid_dev_test(void);
int vid_port_test(void);
int tone_detector_test(void);
int transport_srtp_test(void);

extern pj_pool_factory *mem;
void app_perror(pj_status_t status, const char *title);
Expand Down
Loading
Loading