Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone sdp in sip timer to prevent modification #2476

Merged
merged 2 commits into from
Jul 20, 2020
Merged

Clone sdp in sip timer to prevent modification #2476

merged 2 commits into from
Jul 20, 2020

Conversation

sauwming
Copy link
Member

@sauwming sauwming commented Jul 1, 2020

Fixed #2475.

The patch in this PR is proposed by the creator of the issue above, @mneuhauser.

@sauwming sauwming changed the title Clone sdp to prevent modification Clone sdp in sip timer to prevent modification Jul 1, 2020
@sauwming sauwming self-assigned this Jul 1, 2020
@sauwming sauwming added this to the release-2.11 milestone Jul 1, 2020
@nanangizz
Copy link
Member

The patch looks okay, but perhaps there is a catch.

Without the patch, app modifies sdp_neg->neg_local_sdp directly (i.e: SDP returned by pjmedia_sdp_neg_get_neg_local()), so the modification will be seen by the SDP negotiator and stored in active local SDP if the SDP nego succeeds.

With the patch, app will modify the clone, so the modification won't be seen by SDP negotiator and later pjmedia_sdp_neg_get_active_local() will return the original version (unless app invokes pjmedia_sdp_neg_cancel_offer()+pjmedia_sdp_neg_modify_local_offer()). It may be fine if the modified parts are not to be negotiated (like codec priorities, media direction).

Here is an alternative approach. Currently process_answer() in pjmedia_sdp_neg_negotiate() returns offer param which is neg->neg_local_sdp, and this SDP will be assigned as active local SDP, which will be cloned as sdp_neg->neg_local_sdp in the next SDP offer (this is why the next SDP offer is corrupted). Modifying process_answer() to return a clone of neg->neg_local_sdp or passing a clone of neg->neg_local_sdp to process_answer() should also fix the problem while maintaining the behavior (i.e: SDP negotiator sees any modifications in local SDP). Note that create_answer() clones neg->neg_local_sdp first.

@mneuhauser
Copy link

I've tested the new fix in Asterisk and it worked.

I'm fairly new to the internals of pjsip, so please forgive if that is a stupid/obvious question, but what guarantees that the transmit data pool of the outgoing INVITE (i.e., the one that was used to allocate the modified SDP value) is still in-use/untouched when the SDP is cloned in process_answer()?

From an Asterisk standpoint it would not matter if the original SDP (my version of patch) or the already modified SDP (current version) is used for the >= 2. refresh INVITEs, either works.

@sauwming
Copy link
Member Author

sauwming commented Jul 3, 2020

The pool being used in process_answer() is passed as a parameter, so the function caller needs to make sure that the pool is still valid. In PJSIP, the passed pool is usually call inv's pool, so it should be valid for the duration of the call.

@nanangizz
Copy link
Member

Good question actually. Just rechecked things, currently the transaction will maintain the last transmitted data (tdata) until the transaction is shutdown/destroyed and SDP negotiation is done before the transaction is shutdown. I don't think this behaviour will be changed in the future. Alternatively, perhaps you can use inv->pool_prov, if your PJSIP module somehow can access it, which is used by SIP timer to generate SDP offer (using pjmedia_sdp_neg_send_local_offer()).

@mneuhauser
Copy link

The pool being used in process_answer() is passed as a parameter, so the function caller needs to make sure that the pool is still valid. In PJSIP, the passed pool is usually call inv's pool, so it should be valid for the duration of the call.

I'm not concerned about that pool but about the one that is used to modify the SDP in Asterisk, i.e. pjsip_tx_data->pool.

@mneuhauser
Copy link

Good question actually. Just rechecked things, currently the transaction will maintain the last transmitted data (tdata) until the transaction is shutdown/destroyed and SDP negotiation is done before the transaction is shutdown. I don't think this behaviour will be changed in the future.

For a practical check (just to be extra careful, not that I doubt your analysis), I instrumented pjsip and Asterisk with some log statements to trace the cloning and releasing of the pool in question. Although the two operations happen on different threads, the release always happened after the clone, even when a delay of up to 1 second was introduced with pj_thread_sleep() in process_answer() before the clone operation. That closes the issue for me. I will update the Asterisk change to use the new fix.

Alternatively, perhaps you can use inv->pool_prov, if your PJSIP module somehow can access it, which is used by SIP timer to generate SDP offer (using pjmedia_sdp_neg_send_local_offer()).

I thought about that too, but I do not think that is an option in Asterisk.

Thank you all for your help.

@sauwming sauwming merged commit f5b48f8 into master Jul 20, 2020
@sauwming sauwming deleted the sdp-clone branch July 20, 2020 03:38
asteriskteam pushed a commit to asterisk/asterisk that referenced this pull request Aug 10, 2020
PJSIP, UDP transport with external_media_address and session timers
enabled. Connected to SIP server that is not in local net. Asterisk
initiated the connection and is refreshing the session after 150s
(timeout 300s). The 2nd refresh-INVITE triggered by the pjsip timer has
a malformed IP address in its SDP (garbage string). This only happens
when the SDP is modified by the nat-code to replace the local IP address
with the configured external_media_address.
Analysis: the code to modify the SDP (in
res_pjsip_session.c:session_outgoing_nat_hook() and also (redundantly?)
in res_pjsip_sdp_rtp.c:change_outgoing_sdp_stream_media_address()) uses
the tdata->pool to allocate the replacement string. But the same
pjmedia_sdp_stream that was modified for the 1st refresh-INVITE is also
used for the 2nd refresh-INVITE (because it is stored in pjmedia's
pjmedia_sdp_neg structure). The problem is, that at that moment, the
tdata->pool that holds the stringified external_media_address from the
1. refresh-INVITE has long been reused for something else.
Fix by Sauw Ming of pjproject (see
pjsip/pjproject#2476): the local, potentially
modified pjmedia_sdp_stream is cloned in
pjproject/source/pjsip/src/pjmedia/sip_neg.c:process_answer() and the
clone is stored, thereby detaching from the tdata->pool (which is only
released *after* process_answer())

ASTERISK-28973
Reported-by: Michael Neuhauser

Change-Id: I272ac22436076596e06aa51b9fa23fd1c7734a0e
asteriskteam pushed a commit to asterisk/asterisk that referenced this pull request Aug 10, 2020
PJSIP, UDP transport with external_media_address and session timers
enabled. Connected to SIP server that is not in local net. Asterisk
initiated the connection and is refreshing the session after 150s
(timeout 300s). The 2nd refresh-INVITE triggered by the pjsip timer has
a malformed IP address in its SDP (garbage string). This only happens
when the SDP is modified by the nat-code to replace the local IP address
with the configured external_media_address.
Analysis: the code to modify the SDP (in
res_pjsip_session.c:session_outgoing_nat_hook() and also (redundantly?)
in res_pjsip_sdp_rtp.c:change_outgoing_sdp_stream_media_address()) uses
the tdata->pool to allocate the replacement string. But the same
pjmedia_sdp_stream that was modified for the 1st refresh-INVITE is also
used for the 2nd refresh-INVITE (because it is stored in pjmedia's
pjmedia_sdp_neg structure). The problem is, that at that moment, the
tdata->pool that holds the stringified external_media_address from the
1. refresh-INVITE has long been reused for something else.
Fix by Sauw Ming of pjproject (see
pjsip/pjproject#2476): the local, potentially
modified pjmedia_sdp_stream is cloned in
pjproject/source/pjsip/src/pjmedia/sip_neg.c:process_answer() and the
clone is stored, thereby detaching from the tdata->pool (which is only
released *after* process_answer())

ASTERISK-28973
Reported-by: Michael Neuhauser

Change-Id: I272ac22436076596e06aa51b9fa23fd1c7734a0e
asteriskteam pushed a commit to asterisk/asterisk that referenced this pull request Aug 10, 2020
PJSIP, UDP transport with external_media_address and session timers
enabled. Connected to SIP server that is not in local net. Asterisk
initiated the connection and is refreshing the session after 150s
(timeout 300s). The 2nd refresh-INVITE triggered by the pjsip timer has
a malformed IP address in its SDP (garbage string). This only happens
when the SDP is modified by the nat-code to replace the local IP address
with the configured external_media_address.
Analysis: the code to modify the SDP (in
res_pjsip_session.c:session_outgoing_nat_hook() and also (redundantly?)
in res_pjsip_sdp_rtp.c:change_outgoing_sdp_stream_media_address()) uses
the tdata->pool to allocate the replacement string. But the same
pjmedia_sdp_stream that was modified for the 1st refresh-INVITE is also
used for the 2nd refresh-INVITE (because it is stored in pjmedia's
pjmedia_sdp_neg structure). The problem is, that at that moment, the
tdata->pool that holds the stringified external_media_address from the
1. refresh-INVITE has long been reused for something else.
Fix by Sauw Ming of pjproject (see
pjsip/pjproject#2476): the local, potentially
modified pjmedia_sdp_stream is cloned in
pjproject/source/pjsip/src/pjmedia/sip_neg.c:process_answer() and the
clone is stored, thereby detaching from the tdata->pool (which is only
released *after* process_answer())

ASTERISK-28973
Reported-by: Michael Neuhauser

Change-Id: I272ac22436076596e06aa51b9fa23fd1c7734a0e
asteriskteam pushed a commit to asterisk/asterisk that referenced this pull request Aug 10, 2020
PJSIP, UDP transport with external_media_address and session timers
enabled. Connected to SIP server that is not in local net. Asterisk
initiated the connection and is refreshing the session after 150s
(timeout 300s). The 2nd refresh-INVITE triggered by the pjsip timer has
a malformed IP address in its SDP (garbage string). This only happens
when the SDP is modified by the nat-code to replace the local IP address
with the configured external_media_address.
Analysis: the code to modify the SDP (in
res_pjsip_session.c:session_outgoing_nat_hook() and also (redundantly?)
in res_pjsip_sdp_rtp.c:change_outgoing_sdp_stream_media_address()) uses
the tdata->pool to allocate the replacement string. But the same
pjmedia_sdp_stream that was modified for the 1st refresh-INVITE is also
used for the 2nd refresh-INVITE (because it is stored in pjmedia's
pjmedia_sdp_neg structure). The problem is, that at that moment, the
tdata->pool that holds the stringified external_media_address from the
1. refresh-INVITE has long been reused for something else.
Fix by Sauw Ming of pjproject (see
pjsip/pjproject#2476): the local, potentially
modified pjmedia_sdp_stream is cloned in
pjproject/source/pjsip/src/pjmedia/sip_neg.c:process_answer() and the
clone is stored, thereby detaching from the tdata->pool (which is only
released *after* process_answer())

ASTERISK-28973
Reported-by: Michael Neuhauser

Change-Id: I272ac22436076596e06aa51b9fa23fd1c7734a0e
asteriskteam pushed a commit to asterisk/asterisk that referenced this pull request Aug 10, 2020
PJSIP, UDP transport with external_media_address and session timers
enabled. Connected to SIP server that is not in local net. Asterisk
initiated the connection and is refreshing the session after 150s
(timeout 300s). The 2nd refresh-INVITE triggered by the pjsip timer has
a malformed IP address in its SDP (garbage string). This only happens
when the SDP is modified by the nat-code to replace the local IP address
with the configured external_media_address.
Analysis: the code to modify the SDP (in
res_pjsip_session.c:session_outgoing_nat_hook() and also (redundantly?)
in res_pjsip_sdp_rtp.c:change_outgoing_sdp_stream_media_address()) uses
the tdata->pool to allocate the replacement string. But the same
pjmedia_sdp_stream that was modified for the 1st refresh-INVITE is also
used for the 2nd refresh-INVITE (because it is stored in pjmedia's
pjmedia_sdp_neg structure). The problem is, that at that moment, the
tdata->pool that holds the stringified external_media_address from the
1. refresh-INVITE has long been reused for something else.
Fix by Sauw Ming of pjproject (see
pjsip/pjproject#2476): the local, potentially
modified pjmedia_sdp_stream is cloned in
pjproject/source/pjsip/src/pjmedia/sip_neg.c:process_answer() and the
clone is stored, thereby detaching from the tdata->pool (which is only
released *after* process_answer())

ASTERISK-28973
Reported-by: Michael Neuhauser

Change-Id: I272ac22436076596e06aa51b9fa23fd1c7734a0e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pjmedia_sdp_session exposed to modification by sip session timer callback because of missing clone operation
3 participants