From 383b29dbd8d03be9b63cb7d034cb0165eb615903 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 12 Mar 2019 15:16:58 +0400
Subject: [PATCH] Fix possible crash in calls.

Fixes #5732.
---
 Telegram/SourceFiles/calls/calls_call.cpp     | 103 +++++++++++-------
 Telegram/SourceFiles/calls/calls_call.h       |   2 +-
 Telegram/SourceFiles/calls/calls_instance.cpp | 102 +++++++++--------
 Telegram/SourceFiles/calls/calls_instance.h   |   1 +
 4 files changed, 123 insertions(+), 85 deletions(-)

diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp
index bd63bbf2c..d66c87427 100644
--- a/Telegram/SourceFiles/calls/calls_call.cpp
+++ b/Telegram/SourceFiles/calls/calls_call.cpp
@@ -40,27 +40,27 @@ constexpr auto kMinLayer = 65;
 constexpr auto kHangupTimeoutMs = 5000;
 constexpr auto kSha256Size = 32;
 
-using tgvoip::Endpoint;
-
-void ConvertEndpoint(
-		std::vector<tgvoip::Endpoint> &ep,
-		const MTPDphoneConnection &mtc) {
-	if (mtc.vpeer_tag.v.length() != 16) {
-		return;
-	}
-	auto ipv4 = tgvoip::IPv4Address(std::string(
-		mtc.vip.v.constData(),
-		mtc.vip.v.size()));
-	auto ipv6 = tgvoip::IPv6Address(std::string(
-		mtc.vipv6.v.constData(),
-		mtc.vipv6.v.size()));
-	ep.push_back(Endpoint(
-		(int64_t)mtc.vid.v,
-		(uint16_t)mtc.vport.v,
-		ipv4,
-		ipv6,
-		tgvoip::Endpoint::Type::UDP_RELAY,
-		(unsigned char*)mtc.vpeer_tag.v.data()));
+void AppendEndpoint(
+		std::vector<tgvoip::Endpoint> &list,
+		const MTPPhoneConnection &connection) {
+	connection.match([&](const MTPDphoneConnection &data) {
+		if (data.vpeer_tag.v.length() != 16) {
+			return;
+		}
+		auto ipv4 = tgvoip::IPv4Address(std::string(
+			data.vip.v.constData(),
+			data.vip.v.size()));
+		auto ipv6 = tgvoip::IPv6Address(std::string(
+			data.vipv6.v.constData(),
+			data.vipv6.v.size()));
+		list.emplace_back(
+			(int64_t)data.vid.v,
+			(uint16_t)data.vport.v,
+			ipv4,
+			ipv6,
+			tgvoip::Endpoint::Type::UDP_RELAY,
+			(unsigned char*)data.vpeer_tag.v.data());
+	});
 }
 
 constexpr auto kFingerprintDataSize = 256;
@@ -252,7 +252,8 @@ void Call::actuallyAnswer() {
 	Expects(_type == Type::Incoming);
 
 	if (_state != State::Starting && _state != State::WaitingIncoming) {
-		if (_state != State::ExchangingKeys || !_answerAfterDhConfigReceived) {
+		if (_state != State::ExchangingKeys
+			|| !_answerAfterDhConfigReceived) {
 			return;
 		}
 	}
@@ -271,18 +272,19 @@ void Call::actuallyAnswer() {
 				| MTPDphoneCallProtocol::Flag::f_udp_reflector),
 			MTP_int(kMinLayer),
 			MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()))
-	)).done([this](const MTPphone_PhoneCall &result) {
+	)).done([=](const MTPphone_PhoneCall &result) {
 		Expects(result.type() == mtpc_phone_phoneCall);
 		auto &call = result.c_phone_phoneCall();
 		Auth().data().processUsers(call.vusers);
 		if (call.vphone_call.type() != mtpc_phoneCallWaiting) {
-			LOG(("Call Error: Expected phoneCallWaiting in response to phone.acceptCall()"));
+			LOG(("Call Error: "
+				"Not phoneCallWaiting in response to phone.acceptCall."));
 			finish(FinishType::Failed);
 			return;
 		}
 
 		handleUpdate(call.vphone_call);
-	}).fail([this](const RPCError &error) {
+	}).fail([=](const RPCError &error) {
 		handleRequestError(error);
 	}).send();
 }
@@ -371,7 +373,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 			Unexpected("phoneCallRequested call inside an existing call handleUpdate()");
 		}
 		if (Auth().userId() != data.vparticipant_id.v) {
-			LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(data.vparticipant_id.v).arg(Auth().userId()));
+			LOG(("Call Error: Wrong call participant_id %1, expected %2."
+				).arg(data.vparticipant_id.v
+				).arg(Auth().userId()));
 			finish(FinishType::Failed);
 			return true;
 		}
@@ -379,7 +383,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 		_accessHash = data.vaccess_hash.v;
 		auto gaHashBytes = bytes::make_span(data.vg_a_hash.v);
 		if (gaHashBytes.size() != kSha256Size) {
-			LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(kSha256Size));
+			LOG(("Call Error: Wrong g_a_hash size %1, expected %2."
+				).arg(gaHashBytes.size()
+				).arg(kSha256Size));
 			finish(FinishType::Failed);
 			return true;
 		}
@@ -400,7 +406,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 		if (data.vid.v != _id) {
 			return false;
 		}
-		if (_type == Type::Outgoing && _state == State::Waiting && data.vreceive_date.v != 0) {
+		if (_type == Type::Outgoing
+			&& _state == State::Waiting
+			&& data.vreceive_date.v != 0) {
 			_discardByTimeoutTimer.callOnce(Global::CallRingTimeoutMs());
 			setState(State::Ringing);
 			startWaitingTrack();
@@ -427,16 +435,23 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 		if (data.is_need_debug()) {
 			auto debugLog = _controller ? _controller->GetDebugLog() : std::string();
 			if (!debugLog.empty()) {
-				MTP::send(MTPphone_SaveCallDebug(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_dataJSON(MTP_string(debugLog))));
+				MTP::send(
+					MTPphone_SaveCallDebug(
+						MTP_inputPhoneCall(
+							MTP_long(_id),
+							MTP_long(_accessHash)),
+						MTP_dataJSON(MTP_string(debugLog))));
 			}
 		}
 		if (data.is_need_rating() && _id && _accessHash) {
 			Ui::show(Box<RateCallBox>(_id, _accessHash));
 		}
-		if (data.has_reason() && data.vreason.type() == mtpc_phoneCallDiscardReasonDisconnect) {
+		if (data.has_reason()
+			&& data.vreason.type() == mtpc_phoneCallDiscardReasonDisconnect) {
 			LOG(("Call Info: Discarded with DISCONNECT reason."));
 		}
-		if (data.has_reason() && data.vreason.type() == mtpc_phoneCallDiscardReasonBusy) {
+		if (data.has_reason()
+			&& data.vreason.type() == mtpc_phoneCallDiscardReasonBusy) {
 			setState(State::Busy);
 		} else if (_type == Type::Outgoing || _state == State::HangingUp) {
 			setState(State::Ended);
@@ -451,7 +466,8 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 			return false;
 		}
 		if (_type != Type::Outgoing) {
-			LOG(("Call Error: Unexpected phoneCallAccepted for an incoming call."));
+			LOG(("Call Error: "
+				"Unexpected phoneCallAccepted for an incoming call."));
 			finish(FinishType::Failed);
 		} else if (checkCallFields(data)) {
 			confirmAcceptedCall(data);
@@ -465,8 +481,17 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
 	Expects(_type == Type::Outgoing);
 
-	auto firstBytes = bytes::make_span(call.vg_b.v);
-	auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
+	if (_state == State::ExchangingKeys
+		|| _controller) {
+		LOG(("Call Warning: Unexpected confirmAcceptedCall."));
+		return;
+	}
+
+	const auto firstBytes = bytes::make_span(call.vg_b.v);
+	const auto computedAuthKey = MTP::CreateAuthKey(
+		firstBytes,
+		_randomPower,
+		_dhConfig.p);
 	if (computedAuthKey.empty()) {
 		LOG(("Call Error: Could not compute mod-exp final."));
 		finish(FinishType::Failed);
@@ -561,10 +586,10 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
 	}
 
 	const auto &protocol = call.vprotocol.c_phoneCallProtocol();
-	auto endpoints = std::vector<Endpoint>();
-	ConvertEndpoint(endpoints, call.vconnection.c_phoneConnection());
-	for (int i = 0; i < call.valternative_connections.v.length(); i++) {
-		ConvertEndpoint(endpoints, call.valternative_connections.v[i].c_phoneConnection());
+	auto endpoints = std::vector<tgvoip::Endpoint>();
+	AppendEndpoint(endpoints, call.vconnection);
+	for (const auto &connection : call.valternative_connections.v) {
+		AppendEndpoint(endpoints, connection);
 	}
 
 	auto callbacks = tgvoip::VoIPController::Callbacks();
@@ -602,7 +627,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
 	_controller->SetCallbacks(callbacks);
 	if (Global::UseProxyForCalls()
 		&& (Global::ProxySettings() == ProxyData::Settings::Enabled)) {
-		const auto proxy = Global::SelectedProxy();
+		const auto &proxy = Global::SelectedProxy();
 		if (proxy.supportsCalls()) {
 			Assert(proxy.type == ProxyData::Type::Socks5);
 			_controller->SetProxy(
diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h
index 4f23987f0..04ade9804 100644
--- a/Telegram/SourceFiles/calls/calls_call.h
+++ b/Telegram/SourceFiles/calls/calls_call.h
@@ -121,7 +121,7 @@ public:
 	bytes::vector getKeyShaForFingerprint() const;
 
 	QString getDebugLog() const;
-	
+
 	void setCurrentAudioDevice(bool input, std::string deviceID);
 	void setAudioVolume(bool input, float level);
 	void setAudioDuckingEnabled(bool enabled);
diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp
index ed2a5183d..e215ad5ff 100644
--- a/Telegram/SourceFiles/calls/calls_instance.cpp
+++ b/Telegram/SourceFiles/calls/calls_instance.cpp
@@ -106,10 +106,13 @@ void Instance::destroyCall(not_null<Call*> call) {
 }
 
 void Instance::destroyCurrentPanel() {
-	_pendingPanels.erase(std::remove_if(_pendingPanels.begin(), _pendingPanels.end(), [](auto &&panel) {
-		return !panel;
-	}), _pendingPanels.end());
-	_pendingPanels.push_back(_currentCallPanel.release());
+	_pendingPanels.erase(
+		std::remove_if(
+			_pendingPanels.begin(),
+			_pendingPanels.end(),
+			[](auto &&panel) { return !panel; }),
+		_pendingPanels.end());
+	_pendingPanels.emplace_back(_currentCallPanel.release());
 	_pendingPanels.back()->hideAndDestroy(); // Always queues the destruction.
 }
 
@@ -130,56 +133,65 @@ void Instance::createCall(not_null<UserData*> user, Call::Type type) {
 
 void Instance::refreshDhConfig() {
 	Expects(_currentCall != nullptr);
+
+	const auto weak = base::make_weak(_currentCall);
 	request(MTPmessages_GetDhConfig(
 		MTP_int(_dhConfig.version),
 		MTP_int(MTP::ModExpFirst::kRandomPowerSize)
-	)).done([this, call = base::make_weak(_currentCall)](
-			const MTPmessages_DhConfig &result) {
-		auto random = bytes::const_span();
-		switch (result.type()) {
-		case mtpc_messages_dhConfig: {
-			auto &config = result.c_messages_dhConfig();
-			if (!MTP::IsPrimeAndGood(bytes::make_span(config.vp.v), config.vg.v)) {
-				LOG(("API Error: bad p/g received in dhConfig."));
-				callFailed(call.get());
-				return;
-			}
-			_dhConfig.g = config.vg.v;
-			_dhConfig.p = bytes::make_vector(config.vp.v);
-			random = bytes::make_span(config.vrandom.v);
-		} break;
-
-		case mtpc_messages_dhConfigNotModified: {
-			auto &config = result.c_messages_dhConfigNotModified();
-			random = bytes::make_span(config.vrandom.v);
-			if (!_dhConfig.g || _dhConfig.p.empty()) {
-				LOG(("API Error: dhConfigNotModified on zero version."));
-				callFailed(call.get());
-				return;
-			}
-		} break;
-
-		default: Unexpected("Type in messages.getDhConfig");
-		}
-
-		if (random.size() != MTP::ModExpFirst::kRandomPowerSize) {
-			LOG(("API Error: dhConfig random bytes wrong size: %1").arg(random.size()));
-			callFailed(call.get());
-			return;
-		}
-		if (call) {
-			call->start(random);
-		}
-	}).fail([this, call = base::make_weak(_currentCall)](
-			const RPCError &error) {
+	)).done([=](const MTPmessages_DhConfig &result) {
+		const auto call = weak.get();
+		const auto random = updateDhConfig(result);
 		if (!call) {
-			DEBUG_LOG(("API Warning: call was destroyed before got dhConfig."));
 			return;
 		}
-		callFailed(call.get());
+		if (!random.empty()) {
+			Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
+			call->start(random);
+		} else {
+			callFailed(call);
+		}
+	}).fail([=](const RPCError &error) {
+		const auto call = weak.get();
+		if (!call) {
+			return;
+		}
+		callFailed(call);
 	}).send();
 }
 
+bytes::const_span Instance::updateDhConfig(
+		const MTPmessages_DhConfig &data) {
+	const auto validRandom = [](const QByteArray & random) {
+		if (random.size() != MTP::ModExpFirst::kRandomPowerSize) {
+			return false;
+		}
+		return true;
+	};
+	return data.match([&](const MTPDmessages_dhConfig &data)
+	-> bytes::const_span {
+		auto primeBytes = bytes::make_vector(data.vp.v);
+		if (!MTP::IsPrimeAndGood(primeBytes, data.vg.v)) {
+			LOG(("API Error: bad p/g received in dhConfig."));
+			return {};
+		} else if (!validRandom(data.vrandom.v)) {
+			return {};
+		}
+		_dhConfig.g = data.vg.v;
+		_dhConfig.p = std::move(primeBytes);
+		_dhConfig.version = data.vversion.v;
+		return bytes::make_span(data.vrandom.v);
+	}, [&](const MTPDmessages_dhConfigNotModified &data)
+	-> bytes::const_span {
+		if (!_dhConfig.g || _dhConfig.p.empty()) {
+			LOG(("API Error: dhConfigNotModified on zero version."));
+			return {};
+		} else if (!validRandom(data.vrandom.v)) {
+			return {};
+		}
+		return bytes::make_span(data.vrandom.v);
+	});
+}
+
 void Instance::refreshServerConfig() {
 	if (_serverConfigRequestId) {
 		return;
diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h
index ae45949f9..c457d7f1f 100644
--- a/Telegram/SourceFiles/calls/calls_instance.h
+++ b/Telegram/SourceFiles/calls/calls_instance.h
@@ -60,6 +60,7 @@ private:
 
 	void refreshDhConfig();
 	void refreshServerConfig();
+	bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data);
 
 	bool alreadyInCall();
 	void handleCallUpdate(const MTPPhoneCall &call);