From: Sebastian Kemper Date: Sat, 7 Nov 2020 17:30:47 +0000 (+0100) Subject: asterisk-13.x: fix AST-2020-001 and 002 X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=refs%2Fpull%2F588%2Fhead;p=feed%2Ftelephony.git asterisk-13.x: fix AST-2020-001 and 002 Patches used: http://downloads.asterisk.org/pub/security/AST-2020-001-13.diff http://downloads.asterisk.org/pub/security/AST-2020-002-13.diff Patch AST-2020-002-16.diff was amended a small bit in include/asterisk/res_pjsip_session.h due to Asterisk 13 getting some updates in the meantime which are not in OpenWrt. Both patches refreshed in OpenWrt SDK. Signed-off-by: Sebastian Kemper --- diff --git a/net/asterisk-13.x/Makefile b/net/asterisk-13.x/Makefile index 6d7322a..a624b32 100644 --- a/net/asterisk-13.x/Makefile +++ b/net/asterisk-13.x/Makefile @@ -11,7 +11,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=asterisk13 PKG_VERSION:=13.20.0 -PKG_RELEASE:=5 +PKG_RELEASE:=6 PKG_SOURCE:=asterisk-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://downloads.asterisk.org/pub/telephony/asterisk/releases diff --git a/net/asterisk-13.x/patches/170-AST-2020-001-13.diff b/net/asterisk-13.x/patches/170-AST-2020-001-13.diff new file mode 100644 index 0000000..6499e66 --- /dev/null +++ b/net/asterisk-13.x/patches/170-AST-2020-001-13.diff @@ -0,0 +1,401 @@ +From b4c49adbb9ed22f3ccc4fc45f98421012d6b62a5 Mon Sep 17 00:00:00 2001 +From: Kevin Harwell +Date: Mon, 19 Oct 2020 17:21:57 -0500 +Subject: [PATCH] AST-2020-001 - res_pjsip: Return dialog locked and referenced + +pjproject returns the dialog locked and with a reference. However, +in Asterisk the method that handles this decrements the reference +and removes the lock prior to returning. This makes it possible, +under some circumstances, for another thread to free said dialog +before the thread that created it attempts to use it again. Of +course when the thread that created it tries to use a freed dialog +a crash can occur. + +This patch makes it so Asterisk now returns the newly created +dialog both locked, and with an added reference. This allows the +caller to de-reference, and unlock the dialog when it is safe to +do so. + +In the case of a new SIP Invite the lock, and reference are now +held for the entirety of the new invite handling process. +Otherwise it's possible for the dialog, or its dependent objects, +like the transaction, to disappear. For example if there is a TCP +transport error. + +Change-Id: I5ef645a47829596f402cf383dc02c629c618969e +--- + +--- a/include/asterisk/res_pjsip.h ++++ b/include/asterisk/res_pjsip.h +@@ -1840,6 +1840,11 @@ pjsip_dialog *ast_sip_create_dialog_uac( + /*! + * \brief General purpose method for creating a UAS dialog with an endpoint + * ++ * \deprecated This function is unsafe (due to the returned object not being locked nor ++ * having its reference incremented) and should no longer be used. Instead ++ * use ast_sip_create_dialog_uas_locked so a properly locked and referenced ++ * object is returned. ++ * + * \param endpoint A pointer to the endpoint + * \param rdata The request that is starting the dialog + * \param[out] status On failure, the reason for failure in creating the dialog +@@ -1847,6 +1852,44 @@ pjsip_dialog *ast_sip_create_dialog_uac( + pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status); + + /*! ++ * \brief General purpose method for creating a UAS dialog with an endpoint ++ * ++ * This function creates and returns a locked, and referenced counted pjsip ++ * dialog object. The caller is thus responsible for freeing the allocated ++ * memory, decrementing the reference, and releasing the lock when done with ++ * the returned object. ++ * ++ * \note The safest way to unlock the object, and decrement its reference is by ++ * calling pjsip_dlg_dec_lock. Alternatively, pjsip_dlg_dec_session can be ++ * used to decrement the reference only. ++ * ++ * The dialog is returned locked and with a reference in order to ensure that the ++ * dialog object, and any of its associated objects (e.g. transaction) are not ++ * untimely destroyed. For instance, that could happen when a transport error ++ * occurs. ++ * ++ * As long as the caller maintains a reference to the dialog there should be no ++ * worry that it might unknowningly be destroyed. However, once the caller unlocks ++ * the dialog there is a danger that some of the dialog's internal objects could ++ * be lost and/or compromised. For example, when the aforementioned transport error ++ * occurs the dialog's associated transaction gets destroyed (see pjsip_dlg_on_tsx_state ++ * in sip_dialog.c, and mod_inv_on_tsx_state in sip_inv.c). ++ * ++ * In this case and before using the dialog again the caller should re-lock the ++ * dialog, check to make sure the dialog is still established, and the transaction ++ * still exists and has not been destroyed. ++ * ++ * \param endpoint A pointer to the endpoint ++ * \param rdata The request that is starting the dialog ++ * \param[out] status On failure, the reason for failure in creating the dialog ++ * ++ * \retval A locked, and reference counted pjsip_dialog object. ++ * \retval NULL on failure ++ */ ++pjsip_dialog *ast_sip_create_dialog_uas_locked(const struct ast_sip_endpoint *endpoint, ++ pjsip_rx_data *rdata, pj_status_t *status); ++ ++/*! + * \brief General purpose method for creating an rdata structure using specific information + * \since 13.15.0 + * +--- a/res/res_pjsip.c ++++ b/res/res_pjsip.c +@@ -3293,7 +3293,11 @@ static int uas_use_sips_contact(pjsip_rx + return 0; + } + +-pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status) ++typedef pj_status_t (*create_dlg_uac)(pjsip_user_agent *ua, pjsip_rx_data *rdata, ++ const pj_str_t *contact, pjsip_dialog **p_dlg); ++ ++static pjsip_dialog *create_dialog_uas(const struct ast_sip_endpoint *endpoint, ++ pjsip_rx_data *rdata, pj_status_t *status, create_dlg_uac create_fun) + { + pjsip_dialog *dlg; + pj_str_t contact; +@@ -3328,11 +3332,7 @@ pjsip_dialog *ast_sip_create_dialog_uas( + (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "", + (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : ""); + +-#ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK +- *status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, &contact, &dlg); +-#else +- *status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg); +-#endif ++ *status = create_fun(pjsip_ua_instance(), rdata, &contact, &dlg); + if (*status != PJ_SUCCESS) { + char err[PJ_ERR_MSG_SIZE]; + +@@ -3345,11 +3345,46 @@ pjsip_dialog *ast_sip_create_dialog_uas( + dlg->sess_count++; + pjsip_dlg_set_transport(dlg, &selector); + dlg->sess_count--; ++ ++ return dlg; ++} ++ ++pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status) ++{ + #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK +- pjsip_dlg_dec_lock(dlg); ++ pjsip_dialog *dlg; ++ ++ dlg = create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas_and_inc_lock); ++ if (dlg) { ++ pjsip_dlg_dec_lock(dlg); ++ } ++ ++ return dlg; ++#else ++ return create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas); + #endif ++} ++ ++pjsip_dialog *ast_sip_create_dialog_uas_locked(const struct ast_sip_endpoint *endpoint, ++ pjsip_rx_data *rdata, pj_status_t *status) ++{ ++#ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK ++ return create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas_and_inc_lock); ++#else ++ /* ++ * This is put here in order to be compatible with older versions of pjproject. ++ * Best we can do in this case is immediately lock after getting the dialog. ++ * However, that does leave a "gap" between creating and locking. ++ */ ++ pjsip_dialog *dlg; ++ ++ dlg = create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas); ++ if (dlg) { ++ pjsip_dlg_inc_lock(dlg); ++ } + + return dlg; ++#endif + } + + int ast_sip_create_rdata_with_contact(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port, +--- a/res/res_pjsip_pubsub.c ++++ b/res/res_pjsip_pubsub.c +@@ -1441,7 +1441,7 @@ static struct sip_subscription_tree *cre + } + sub_tree->role = AST_SIP_NOTIFIER; + +- dlg = ast_sip_create_dialog_uas(endpoint, rdata, dlg_status); ++ dlg = ast_sip_create_dialog_uas_locked(endpoint, rdata, dlg_status); + if (!dlg) { + if (*dlg_status != PJ_EEXISTS) { + ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n"); +@@ -1462,8 +1462,16 @@ static struct sip_subscription_tree *cre + } + + pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub); ++ + subscription_setup_dialog(sub_tree, dlg); + ++ /* ++ * The evsub and subscription setup both add dialog refs, so the dialog ref that ++ * was added when the dialog was created (see ast_sip_create_dialog_uas_lock) can ++ * now be removed. The lock should no longer be needed so can be removed too. ++ */ ++ pjsip_dlg_dec_lock(dlg); ++ + #ifdef HAVE_PJSIP_EVSUB_GRP_LOCK + pjsip_evsub_add_ref(sub_tree->evsub); + #endif +--- a/res/res_pjsip_session.c ++++ b/res/res_pjsip_session.c +@@ -2050,6 +2050,75 @@ static enum sip_get_destination_result g + return SIP_GET_DEST_EXTEN_NOT_FOUND; + } + ++/* ++ * /internal ++ * /brief Process initial answer for an incoming invite ++ * ++ * This function should only be called during the setup, and handling of a ++ * new incoming invite. Most, if not all of the time, this will be called ++ * when an error occurs and we need to respond as such. ++ * ++ * When a SIP session termination code is given for the answer it's assumed ++ * this call then will be the final bit of processing before ending session ++ * setup. As such, we've been holding a lock, and a reference on the invite ++ * session's dialog. So before returning this function removes that reference, ++ * and unlocks the dialog. ++ * ++ * \param inv_session The session on which to answer ++ * \param rdata The original request ++ * \param answer_code The answer's numeric code ++ * \param terminate_code The termination code if the answer fails ++ * \param notify Whether or not to call on_state_changed ++ * ++ * \retval 0 if invite successfully answered, -1 if an error occurred ++ */ ++static int new_invite_initial_answer(pjsip_inv_session *inv_session, pjsip_rx_data *rdata, ++ int answer_code, int terminate_code, pj_bool_t notify) ++{ ++ pjsip_tx_data *tdata = NULL; ++ int res = 0; ++ ++ if (inv_session->state != PJSIP_INV_STATE_DISCONNECTED) { ++ if (pjsip_inv_initial_answer( ++ inv_session, rdata, answer_code, NULL, NULL, &tdata) != PJ_SUCCESS) { ++ ++ pjsip_inv_terminate(inv_session, terminate_code ? terminate_code : answer_code, notify); ++ res = -1; ++ } else { ++ pjsip_inv_send_msg(inv_session, tdata); ++ } ++ } ++ ++ if (answer_code >= 300) { ++ /* ++ * A session is ending. The dialog has a reference that needs to be ++ * removed and holds a lock that needs to be unlocked before returning. ++ */ ++ pjsip_dlg_dec_lock(inv_session->dlg); ++ } ++ ++ return res; ++} ++ ++/* ++ * /internal ++ * /brief Create and initialize a pjsip invite session ++ ++ * pjsip_inv_session adds, and maintains a reference to the dialog upon a successful ++ * invite session creation until the session is destroyed. However, we'll wait to ++ * remove the reference that was added for the dialog when it gets created since we're ++ * not ready to unlock the dialog in this function. ++ * ++ * So, if this function successfully returns that means it returns with its newly ++ * created, and associated dialog locked and with two references (i.e. dialog's ++ * reference count should be 2). ++ * ++ * \param endpoint A pointer to the endpoint ++ * \param rdata The request that is starting the dialog ++ * ++ * \retval A pjsip invite session object ++ * \retval NULL on error ++ */ + static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct ast_sip_endpoint *endpoint) + { + pjsip_tx_data *tdata; +@@ -2068,15 +2137,28 @@ static pjsip_inv_session *pre_session_se + } + return NULL; + } +- dlg = ast_sip_create_dialog_uas(endpoint, rdata, &dlg_status); ++ ++ dlg = ast_sip_create_dialog_uas_locked(endpoint, rdata, &dlg_status); + if (!dlg) { + if (dlg_status != PJ_EEXISTS) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); + } + return NULL; + } ++ ++ /* ++ * The returned dialog holds a lock and has a reference added. Any paths where the ++ * dialog invite session is not returned must unlock the dialog and remove its reference. ++ */ ++ + if (pjsip_inv_create_uas(dlg, rdata, NULL, options, &inv_session) != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); ++ /* ++ * The acquired dialog holds a lock, and a reference. Since the dialog is not ++ * going to be returned here it must first be unlocked and de-referenced. This ++ * must be done prior to calling dialog termination. ++ */ ++ pjsip_dlg_dec_lock(dlg); + pjsip_dlg_terminate(dlg); + return NULL; + } +@@ -2085,12 +2167,13 @@ static pjsip_inv_session *pre_session_se + inv_session->sdp_neg_flags = PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE; + #endif + if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) { +- if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) != PJ_SUCCESS) { +- pjsip_inv_terminate(inv_session, 500, PJ_FALSE); +- } +- pjsip_inv_send_msg(inv_session, tdata); ++ /* Dialog's lock and a reference are removed in new_invite_initial_answer */ ++ new_invite_initial_answer(inv_session, rdata, 500, 500, PJ_FALSE); ++ /* Remove 2nd reference added at inv_session creation */ ++ pjsip_dlg_dec_session(inv_session->dlg, &session_module); + return NULL; + } ++ + return inv_session; + } + +@@ -2220,7 +2303,6 @@ static void handle_new_invite_request(pj + { + RAII_VAR(struct ast_sip_endpoint *, endpoint, + ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); +- pjsip_tx_data *tdata = NULL; + pjsip_inv_session *inv_session = NULL; + struct ast_sip_session *session; + struct new_invite invite; +@@ -2233,27 +2315,48 @@ static void handle_new_invite_request(pj + return; + } + ++ /* ++ * Upon a successful pre_session_setup the associated dialog is returned locked ++ * and with an added reference. Well actually two references. One added when the ++ * dialog itself was created, and another added when the pjsip invite session was ++ * created and the dialog was added to it. ++ * ++ * In order to ensure the dialog's, and any of its internal attributes, lifetimes ++ * we'll hold the lock and maintain the reference throughout the entire new invite ++ * handling process. See ast_sip_create_dialog_uas_locked for more details but, ++ * basically we do this to make sure a transport failure does not destroy the dialog ++ * and/or transaction out from underneath us between pjsip calls. Alternatively, we ++ * could probably release the lock if we needed to, but then we'd have to re-lock and ++ * check the dialog and transaction prior to every pjsip call. ++ * ++ * That means any off nominal/failure paths in this function must remove the associated ++ * dialog reference added at dialog creation, and remove the lock. As well the ++ * referenced pjsip invite session must be "cleaned up", which should also then ++ * remove its reference to the dialog at that time. ++ * ++ * Nominally we'll unlock the dialog, and release the reference when all new invite ++ * process handling has successfully completed. ++ */ ++ + #ifdef HAVE_PJSIP_INV_SESSION_REF + if (pjsip_inv_add_ref(inv_session) != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Can't increase the session reference counter\n"); +- if (inv_session->state != PJSIP_INV_STATE_DISCONNECTED) { +- if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) { +- pjsip_inv_terminate(inv_session, 500, PJ_FALSE); +- } else { +- pjsip_inv_send_msg(inv_session, tdata); +- } ++ /* Dialog's lock and a reference are removed in new_invite_initial_answer */ ++ if (!new_invite_initial_answer(inv_session, rdata, 500, 500, PJ_FALSE)) { ++ /* Terminate the session if it wasn't done in the answer */ ++ pjsip_inv_terminate(inv_session, 500, PJ_FALSE); + } + return; + } + #endif +- + session = ast_sip_session_alloc(endpoint, NULL, inv_session, rdata); + if (!session) { +- if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) { ++ /* Dialog's lock and reference are removed in new_invite_initial_answer */ ++ if (!new_invite_initial_answer(inv_session, rdata, 500, 500, PJ_FALSE)) { ++ /* Terminate the session if it wasn't done in the answer */ + pjsip_inv_terminate(inv_session, 500, PJ_FALSE); +- } else { +- pjsip_inv_send_msg(inv_session, tdata); + } ++ + #ifdef HAVE_PJSIP_INV_SESSION_REF + pjsip_inv_dec_ref(inv_session); + #endif +@@ -2271,6 +2374,17 @@ static void handle_new_invite_request(pj + invite.rdata = rdata; + new_invite(&invite); + ++ /* ++ * The dialog lock and reference added at dialog creation time must be ++ * maintained throughout the new invite process. Since we're pretty much ++ * done at this point with things it's safe to go ahead and remove the lock ++ * and the reference here. See ast_sip_create_dialog_uas_locked for more info. ++ * ++ * Note, any future functionality added that does work using the dialog must ++ * be done before this. ++ */ ++ pjsip_dlg_dec_lock(inv_session->dlg); ++ + ao2_ref(session, -1); + } + diff --git a/net/asterisk-13.x/patches/180-AST-2020-002-13.diff b/net/asterisk-13.x/patches/180-AST-2020-002-13.diff new file mode 100644 index 0000000..80339d3 --- /dev/null +++ b/net/asterisk-13.x/patches/180-AST-2020-002-13.diff @@ -0,0 +1,107 @@ +From 01b7ac0d590b0ad2e3e856d1a81fc87154ae68a0 Mon Sep 17 00:00:00 2001 +From: Ben Ford +Date: Mon, 02 Nov 2020 10:29:31 -0600 +Subject: [PATCH] AST-2020-002 - res_pjsip: Stop sending INVITEs after challenge limit. + +If Asterisk sends out an INVITE and receives a challenge with a +different nonce value each time, it will continuously send out INVITEs, +even if the call is hung up. The endpoint must be configured for +outbound authentication for this to occur. A limit has been set on +outbound INVITEs so that, once reached, Asterisk will stop sending +INVITEs and the transaction will terminate. + +ASTERISK-29013 + +Change-Id: I2d001ca745b00ca8aa12030f2240cd72363b46f7 +--- + +--- a/include/asterisk/res_pjsip.h ++++ b/include/asterisk/res_pjsip.h +@@ -64,6 +64,9 @@ struct pjsip_tpselector; + /*! \brief Maximum number of ciphers supported for a TLS transport */ + #define SIP_TLS_MAX_CIPHERS 64 + ++/*! Maximum number of challenges before assuming that we are in a loop */ ++#define MAX_RX_CHALLENGES 10 ++ + /*! + * \brief Structure for SIP transport information + */ +--- a/include/asterisk/res_pjsip_session.h ++++ b/include/asterisk/res_pjsip_session.h +@@ -161,6 +161,8 @@ struct ast_sip_session { + enum ast_sip_dtmf_mode dtmf; + /*! Initial incoming INVITE Request-URI. NULL otherwise. */ + pjsip_uri *request_uri; ++ /*! Number of challenges received during outgoing requests to determine if we are in a loop */ ++ unsigned int authentication_challenge_count:4; + }; + + typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata); +--- a/res/res_pjsip.c ++++ b/res/res_pjsip.c +@@ -3693,8 +3693,6 @@ static pj_bool_t does_method_match(const + return pj_stristr(&method, message_method) ? PJ_TRUE : PJ_FALSE; + } + +-/*! Maximum number of challenges before assuming that we are in a loop */ +-#define MAX_RX_CHALLENGES 10 + #define TIMER_INACTIVE 0 + #define TIMEOUT_TIMER2 5 + +--- a/res/res_pjsip_session.c ++++ b/res/res_pjsip_session.c +@@ -1184,7 +1184,6 @@ static pjsip_module session_reinvite_mod + .on_rx_request = session_reinvite_on_rx_request, + }; + +- + void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip_tx_data *tdata, + ast_sip_session_response_cb on_response) + { +@@ -1470,12 +1469,17 @@ struct ast_sip_session *ast_sip_session_ + ao2_ref(session, -1); + return NULL; + } ++ ++ /* Track the number of challenges received on outbound requests */ ++ session->authentication_challenge_count = 0; ++ + AST_LIST_TRAVERSE(&session->supplements, iter, next) { + if (iter->session_begin) { + iter->session_begin(session); + } + } + ++ + /* Avoid unnecessary ref manipulation to return a session */ + ret_session = session; + session = NULL; +@@ -1642,6 +1646,11 @@ static pj_bool_t outbound_invite_auth(pj + + session = inv->mod_data[session_module.id]; + ++ if (++session->authentication_challenge_count > MAX_RX_CHALLENGES) { ++ ast_debug(3, "Initial INVITE reached maximum number of auth attempts.\n"); ++ return PJ_FALSE; ++ } ++ + if (ast_sip_create_request_with_auth(&session->endpoint->outbound_auths, rdata, tsx, + &tdata)) { + return PJ_FALSE; +@@ -2888,6 +2897,7 @@ static void session_inv_on_tsx_state_cha + ast_debug(1, "reINVITE received final response code %d\n", + tsx->status_code); + if ((tsx->status_code == 401 || tsx->status_code == 407) ++ && ++session->authentication_challenge_count < MAX_RX_CHALLENGES + && !ast_sip_create_request_with_auth( + &session->endpoint->outbound_auths, + e->body.tsx_state.src.rdata, tsx, &tdata)) { +@@ -2962,6 +2972,7 @@ static void session_inv_on_tsx_state_cha + (int) pj_strlen(&tsx->method.name), pj_strbuf(&tsx->method.name), + tsx->status_code); + if ((tsx->status_code == 401 || tsx->status_code == 407) ++ && ++session->authentication_challenge_count < MAX_RX_CHALLENGES + && !ast_sip_create_request_with_auth( + &session->endpoint->outbound_auths, + e->body.tsx_state.src.rdata, tsx, &tdata)) {