From 0d1a7ab224a8d47642fc447d1237e5a275dafb57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Tue, 16 Nov 2021 14:04:58 -0500
Subject: [PATCH] ice transport: avoid deadlock in send callback

deadlock could happen with:
* handleEvents -> pj_lock -> on_data_sent -> iceMutex_
* send() -> iceMutex_ -> pj_ice_strans_sendto2 -> pj_lock

This was fixed by:
* Not setting on_data_sent callback for UDP
* Using a different mutex than iceMutex_ to wait for data send

Change-Id: Ic50698ac9dfe37574f145baa718fff8b74cc99be
---
 src/ice_transport.cpp | 32 +++++++++++++++++++-------------
 src/ice_transport.h   |  1 -
 2 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp
index cd7cdca7d2..d932021053 100644
--- a/src/ice_transport.cpp
+++ b/src/ice_transport.cpp
@@ -140,7 +140,6 @@ public:
     bool upnpEnabled_ {false};
     IceTransportCompleteCb on_initdone_cb_ {};
     IceTransportCompleteCb on_negodone_cb_ {};
-    IceRecvInfo on_recv_cb_ {};
     mutable std::mutex iceMutex_ {};
     pj_ice_strans* icest_ {nullptr};
     unsigned streamsCount_ {0};
@@ -211,9 +210,9 @@ public:
     std::atomic_bool threadTerminateFlags_ {false};
 
     // Wait data on components
-    pj_size_t lastSentLen_ {};
+    mutable std::mutex sendDataMutex_ {};
     std::condition_variable waitDataCv_ = {};
-
+    pj_size_t lastSentLen_ {0};
     std::atomic_bool destroying_ {false};
     onShutdownCb scb {};
 };
@@ -474,17 +473,20 @@ IceTransport::Impl::initIceInstance(const IceTransportOptions& options)
             JAMI_WARN("null IceTransport");
     };
 
-    icecb.on_data_sent = [](pj_ice_strans* ice_st, pj_ssize_t size) {
-        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) {
-            std::lock_guard<std::mutex> lk(tr->iceMutex_);
-            tr->lastSentLen_ += size;
-            tr->waitDataCv_.notify_all();
-        } else
-            JAMI_WARN("null IceTransport");
-    };
+    if (isTcp_) {
+        icecb.on_data_sent = [](pj_ice_strans* ice_st, pj_ssize_t size) {
+            if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) {
+                std::lock_guard lk(tr->sendDataMutex_);
+                tr->lastSentLen_ += size;
+                tr->waitDataCv_.notify_all();
+            } else
+                JAMI_WARN("null IceTransport");
+        };
+    }
 
     icecb.on_destroy = [](pj_ice_strans* ice_st) {
         if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) {
+            std::lock_guard lk(tr->sendDataMutex_);
             tr->destroying_ = true;
             tr->waitDataCv_.notify_all();
             if (tr->scb)
@@ -1681,12 +1683,16 @@ IceTransport::send(unsigned compId, const unsigned char* buf, size_t len)
         return -1;
     }
 
-    std::unique_lock lk(pimpl_->iceMutex_);
+    std::lock_guard lk(pimpl_->iceMutex_);
 
     if (not pimpl_->icest_) {
         return -1;
     }
 
+    std::unique_lock dlk(pimpl_->sendDataMutex_, std::defer_lock);
+    if (isTCPEnabled())
+        dlk.lock();
+
     auto status = pj_ice_strans_sendto2(pimpl_->icest_,
                                         compId,
                                         buf,
@@ -1697,7 +1703,7 @@ IceTransport::send(unsigned compId, const unsigned char* buf, size_t len)
     if (status == PJ_EPENDING && isTCPEnabled()) {
         // NOTE; because we are in TCP, the sent size will count the header (2
         // bytes length).
-        pimpl_->waitDataCv_.wait(lk, [&] {
+        pimpl_->waitDataCv_.wait(dlk, [&] {
             return pimpl_->lastSentLen_ >= static_cast<pj_size_t>(len)
                    or pimpl_->destroying_.load();
         });
diff --git a/src/ice_transport.h b/src/ice_transport.h
index 06f77ed898..4bee84740c 100644
--- a/src/ice_transport.h
+++ b/src/ice_transport.h
@@ -41,7 +41,6 @@ class Controller;
 class IceTransport;
 
 using IceTransportCompleteCb = std::function<void(bool)>;
-using IceRecvInfo = std::function<void(void)>;
 using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>;
 using IceCandidate = pj_ice_sess_cand;
 using onShutdownCb = std::function<void(void)>;
-- 
GitLab