]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_fax: Fix deadlock in ast_channel_get_t38_state(). 05/3705/1
authorRichard Mudgett <rmudgett@digium.com>
Tue, 23 Aug 2016 16:02:35 +0000 (11:02 -0500)
committerRichard Mudgett <rmudgett@digium.com>
Thu, 25 Aug 2016 23:22:21 +0000 (18:22 -0500)
ast_channel_get_t38_state() calls ast_channel_queryoption() with
AST_OPTION_T38_STATE.  If the passed in channel is a local channel then a
deadlock can happen if a channel lock is held when called.

* Made ast_channel_get_t38_state() callers not hold a channel lock before
calling.

* Update ast_channel_get_t38_state() doxygen to note that no channel locks
can be held when calling the function.

ASTERISK-26203 #close
Reported by: Etienne Lessard

ASTERISK-24822 #close
Reported by: David Brillert

ASTERISK-22732 #close
Reported by: Richard Mudgett

Change-Id: I49fd76fa9af628b4198009b5c0b82c8b03681214

include/asterisk/channel.h
res/res_fax.c

index 02aee5e9591c6ba4174a7b384f694fd164f1b1bc..d8581ff7f542c3bfc17883959d5a9b6963a738e6 100644 (file)
@@ -2439,7 +2439,11 @@ static inline int ast_fdisset(struct pollfd *pfds, int fd, int maximum, int *sta
        return 0;
 }
 
-/*! \brief Retrieves the current T38 state of a channel */
+/*!
+ * \brief Retrieves the current T38 state of a channel
+ *
+ * \note Absolutely _NO_ channel locks should be held before calling this function.
+ */
 static inline enum ast_t38_state ast_channel_get_t38_state(struct ast_channel *chan)
 {
        enum ast_t38_state state = T38_STATE_UNAVAILABLE;
index 83ca28280af5ed1846c7c75b4a3cd9989c10125e..833a3c8eec25233bff751b9178c73e4d4c1e6e25 100644 (file)
@@ -2686,8 +2686,14 @@ static struct ast_frame *fax_gateway_detect_v21(struct fax_gateway *gateway, str
        }
 
        if (gateway->detected_v21) {
+               enum ast_t38_state state_other;
+
                destroy_v21_sessions(gateway);
-               if (ast_channel_get_t38_state(other) == T38_STATE_UNKNOWN) {
+
+               ast_channel_unlock(chan);
+               state_other = ast_channel_get_t38_state(other);
+               ast_channel_lock(chan);
+               if (state_other == T38_STATE_UNKNOWN) {
                        ast_debug(1, "detected v21 preamble from %s\n", ast_channel_name(active));
                        return fax_gateway_request_t38(gateway, chan, f);
                } else {
@@ -2728,6 +2734,7 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
        struct ast_control_t38_parameters *control_params = f->data.ptr;
        struct ast_channel *other = (active == chan) ? peer : chan;
        struct ast_fax_session_details *details;
+       enum ast_t38_state state_other;
 
        if (f->datalen != sizeof(struct ast_control_t38_parameters)) {
                /* invalaid AST_CONTROL_T38_PARAMETERS frame, we can't
@@ -2750,9 +2757,11 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
        }
 
        if (control_params->request_response == AST_T38_REQUEST_NEGOTIATE) {
-               enum ast_t38_state state = ast_channel_get_t38_state(other);
+               ast_channel_unlock(chan);
+               state_other = ast_channel_get_t38_state(other);
+               ast_channel_lock(chan);
 
-               if (state == T38_STATE_UNKNOWN) {
+               if (state_other == T38_STATE_UNKNOWN) {
                        /* we detected a request to negotiate T.38 and the
                         * other channel appears to support T.38, we'll pass
                         * the request through and only step in if the other
@@ -2764,7 +2773,7 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
                        details->gateway_timeout = FAX_GATEWAY_TIMEOUT;
                        ao2_ref(details, -1);
                        return f;
-               } else if (state == T38_STATE_UNAVAILABLE || state == T38_STATE_REJECTED) {
+               } else if (state_other == T38_STATE_UNAVAILABLE || state_other == T38_STATE_REJECTED) {
                        /* the other channel does not support T.38, we need to
                         * step in here */
                        ast_debug(1, "%s is attempting to negotiate T.38 but %s does not support it\n", ast_channel_name(active), ast_channel_name(other));
@@ -2839,7 +2848,10 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
                /* our request to negotiate T.38 was refused, if the other
                 * channel supports T.38, they might still reinvite and save
                 * the day.  Otherwise disable the gateway. */
-               if (ast_channel_get_t38_state(other) == T38_STATE_UNKNOWN) {
+               ast_channel_unlock(chan);
+               state_other = ast_channel_get_t38_state(other);
+               ast_channel_lock(chan);
+               if (state_other == T38_STATE_UNKNOWN) {
                        gateway->t38_state = T38_STATE_UNAVAILABLE;
                } else {
                        ast_framehook_detach(chan, details->gateway_id);
@@ -3039,8 +3051,16 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
        ao2_ref(peer, +1);
 
        if (!gateway->bridged) {
+               enum ast_t38_state state_chan;
+               enum ast_t38_state state_peer;
+
+               ast_channel_unlock(chan);
+               state_chan = ast_channel_get_t38_state(chan);
+               state_peer = ast_channel_get_t38_state(peer);
+               ast_channel_lock(chan);
+
                /* don't start a gateway if neither channel can handle T.38 */
-               if (ast_channel_get_t38_state(chan) == T38_STATE_UNAVAILABLE && ast_channel_get_t38_state(peer) == T38_STATE_UNAVAILABLE) {
+               if (state_chan == T38_STATE_UNAVAILABLE && state_peer == T38_STATE_UNAVAILABLE) {
                        ast_debug(1, "not starting gateway for %s and %s; neither channel supports T.38\n", ast_channel_name(chan), ast_channel_name(peer));
                        ast_framehook_detach(chan, gateway->framehook);
                        details->gateway_id = -1;