tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit e3daec3702c386b513494da7c35fc2a3cd18a9e3
parent 93b6bd7e5a851bf60a188acedbffa13198a6c7c3
Author: Byron Campen <docfaraday@gmail.com>
Date:   Mon,  8 Dec 2025 23:01:40 +0000

Bug 1999831: Fix bugs that can break id reuse. r=ng

Includes:
- Fix bug where a new channel can be prematurely removed from mChannels
  due to the closing of a previous channel with the same id.
- Do not transition to open until our open request gets an ack.
- Simplify SendDataMessage a bit.

Differential Revision: https://phabricator.services.mozilla.com/D272355

Diffstat:
Mnetwerk/sctp/datachannel/DataChannel.cpp | 47+++++++++++++++++++++++++++++------------------
Mnetwerk/sctp/datachannel/DataChannel.h | 2+-
Mtesting/web-platform/tests/webrtc-stats/getStats-remote-candidate-address.html | 36++++++++++--------------------------
3 files changed, 40 insertions(+), 45 deletions(-)

diff --git a/netwerk/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp @@ -270,11 +270,13 @@ bool DataChannelConnection::ConnectToTransport(const std::string& aTransportId, if (id != INVALID_STREAM) { channel->SetStream(id); mChannels.Insert(channel); - DC_DEBUG(("%p: Inserting auto-selected id %u", this, - static_cast<unsigned>(id))); + DC_DEBUG(("%p: Inserting auto-selected id %u for channel %p", this, + static_cast<unsigned>(id), channel.get())); mStreamIds.InsertElementSorted(id); hasStreamId.AppendElement(std::move(channel)); } else { + DC_WARN(("%p: Could not find id for channel %p, calling AnnounceClosed", + this, channel.get())); // Spec language is very similar to AnnounceClosed, the differences // being a lack of a closed check at the top, a different error event, // and no removal of the channel from the [[DataChannels]] slot. @@ -443,6 +445,7 @@ int DataChannelConnection::SendControlMessage(DataChannel& aChannel, // Returns a POSIX error code. int DataChannelConnection::SendOpenAckMessage(DataChannel& aChannel) { MOZ_ASSERT(mSTS->IsOnCurrentThread()); + DC_INFO(("%p: Sending DataChannel open ack, channel %p", this, &aChannel)); struct rtcweb_datachannel_ack ack = {}; ack.msg_type = DATA_CHANNEL_ACK; @@ -451,6 +454,8 @@ int DataChannelConnection::SendOpenAckMessage(DataChannel& aChannel) { // Returns a POSIX error code. int DataChannelConnection::SendOpenRequestMessage(DataChannel& aChannel) { + DC_INFO( + ("%p: Sending DataChannel open request, channel %p", this, &aChannel)); const nsACString& label = aChannel.mLabel; const nsACString& protocol = aChannel.mProtocol; const bool unordered = !aChannel.mOrdered; @@ -658,6 +663,10 @@ void DataChannelConnection::HandleOpenAckMessage( channel.get(), stream, channel->mWaitingForAck ? 1 : 0)); channel->mWaitingForAck = false; + + // Either externally negotiated or we sent Open + channel->AnnounceOpen(); + OnStreamOpen(stream); } // Caller must ensure that length <= SIZE_MAX @@ -980,10 +989,11 @@ void DataChannelConnection::OpenFinish(RefPtr<DataChannel> aChannel) { aChannel->mSendStreamNeedsReset = true; aChannel->mRecvStreamNeedsReset = true; - // Either externally negotiated or we sent Open - // FIX? Move into DOMDataChannel? I don't think we can send it yet here - aChannel->AnnounceOpen(); - OnStreamOpen(stream); + if (aChannel->mNegotiated) { + // Either externally negotiated or we sent Open + aChannel->AnnounceOpen(); + OnStreamOpen(stream); + } } nsISerialEventTarget* DataChannelConnection::GetIOThread() { @@ -1032,7 +1042,7 @@ void DataChannelConnection::SetState(DataChannelConnectionState aState) { } } -void DataChannelConnection::SendDataMessage(uint16_t aStream, nsACString&& aMsg, +void DataChannelConnection::SendDataMessage(DataChannel& aChannel, nsACString&& aMsg, bool aIsBinary) { // Could be main, could be a worker @@ -1041,14 +1051,8 @@ void DataChannelConnection::SendDataMessage(uint16_t aStream, nsACString&& aMsg, mSTS->Dispatch( NS_NewCancelableRunnableFunction( __func__, - [this, self = RefPtr<DataChannelConnection>(this), aStream, + [this, self = RefPtr<DataChannelConnection>(this), channel = RefPtr(&aChannel), msg = std::move(temp), aIsBinary]() mutable { - RefPtr<DataChannel> channel = FindChannelByStream(aStream); - if (!channel) { - // Must have closed due to a transport error? - return; - } - Maybe<uint16_t> maxRetransmissions; Maybe<uint16_t> maxLifetimeMs; @@ -1256,7 +1260,14 @@ bool DataChannelConnection::Channels::Remove( return mChannels.RemoveElement(aChannel); } - return mChannels.RemoveElementSorted(aChannel, IdComparator()); + auto index = mChannels.BinaryIndexOf(aChannel->mStream, IdComparator()); + if (index != ChannelArray::NoIndex) { + if (mChannels[index].get() == aChannel.get()) { + mChannels.RemoveElementAt(index); + return true; + } + } + return false; } RefPtr<DataChannel> DataChannelConnection::Channels::Get(uint16_t aId) const { @@ -1513,7 +1524,7 @@ void DataChannel::SendBuffer(nsCString&& aMsg, bool aBinary) { aBinary]( const GenericNonExclusivePromise::ResolveOrRejectValue&) mutable { if (mConnection) { - mConnection->SendDataMessage(mStream, std::move(msg), aBinary); + mConnection->SendDataMessage(*this, std::move(msg), aBinary); return GenericNonExclusivePromise::CreateAndResolve(true, __func__); } return GenericNonExclusivePromise::CreateAndResolve(false, __func__); @@ -1522,7 +1533,7 @@ void DataChannel::SendBuffer(nsCString&& aMsg, bool aBinary) { UnsetMessagesSentPromiseWhenSettled(); return; } - mConnection->SendDataMessage(mStream, std::move(aMsg), aBinary); + mConnection->SendDataMessage(*this, std::move(aMsg), aBinary); } void DataChannel::SendBinaryBlob(nsIInputStream* aBlob) { @@ -1540,7 +1551,7 @@ void DataChannel::SendBinaryBlob(nsIInputStream* aBlob) { if (NS_SUCCEEDED(NS_ReadInputStreamToString(blob, data, -1))) { if (mConnection) { // This dispatches to STS, which is when we're supposed to resolve - mConnection->SendDataMessage(mStream, std::move(data), true); + mConnection->SendDataMessage(*this, std::move(data), true); } blob->Close(); return GenericNonExclusivePromise::CreateAndResolve(true, __func__); diff --git a/netwerk/sctp/datachannel/DataChannel.h b/netwerk/sctp/datachannel/DataChannel.h @@ -279,7 +279,7 @@ class DataChannelConnection : public net::NeckoTargetHolder { nsISerialEventTarget* aTarget, MediaTransportHandler* aHandler); - void SendDataMessage(uint16_t aStream, nsACString&& aMsg, bool aIsBinary); + void SendDataMessage(DataChannel& aChannel, nsACString&& aMsg, bool aIsBinary); DataChannelConnectionState GetState() const { MOZ_ASSERT(mSTS->IsOnCurrentThread()); diff --git a/testing/web-platform/tests/webrtc-stats/getStats-remote-candidate-address.html b/testing/web-platform/tests/webrtc-stats/getStats-remote-candidate-address.html @@ -1,9 +1,11 @@ <!doctype html> <meta charset=utf-8> +<meta name="timeout" content="long"> <title>Exposure or remote candidate address on stats</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../webrtc/RTCPeerConnection-helper.js"></script> +<script src="../webrtc/RTCDataChannel-helper.js"></script> <script> promise_test(async (test) => { const localPc = new RTCPeerConnection(); @@ -11,26 +13,19 @@ promise_test(async (test) => { const remotePc = new RTCPeerConnection(); test.add_cleanup(() => remotePc.close()); - const promiseDataChannel = new Promise(resolve => { - remotePc.addEventListener('datachannel', (event) => { - resolve(event.channel); - }); - }); - - const localDataChannel = localPc.createDataChannel('test'); - + const pairPromise = openChannelPair(localPc, remotePc, 'prflx_obfuscate'); localPc.addEventListener('icecandidate', event => { if (event.candidate) remotePc.addIceCandidate(event.candidate); }); exchangeOfferAnswer(localPc, remotePc); - const remoteDataChannel = await promiseDataChannel; + const [localChannel, remoteChannel] = await pairPromise; - localDataChannel.send("test"); + localChannel.send("test"); await new Promise(resolve => { - remoteDataChannel.onmessage = resolve; + remoteChannel.onmessage = resolve; }); const remoteCandidateStats = [...(await localPc.getStats()).values()].find(({type}) => type === 'remote-candidate'); @@ -43,30 +38,19 @@ promise_test(async (test) => { const remotePc = new RTCPeerConnection(); test.add_cleanup(() => remotePc.close()); - const promiseDataChannel = new Promise(resolve => { - remotePc.addEventListener('datachannel', (event) => { - resolve(event.channel); - }); - }); - - const localDataChannel = localPc.createDataChannel('test'); - + const pairPromise = openChannelPair(localPc, remotePc, 'prflx_obfuscate'); localPc.addEventListener('icecandidate', event => { if (event.candidate) remotePc.addIceCandidate(event.candidate); }); - remotePc.addEventListener('icecandidate', event => { - if (event.candidate) - localPc.addIceCandidate(event.candidate); - }); exchangeOfferAnswer(localPc, remotePc); - const remoteDataChannel = await promiseDataChannel; + const [localChannel, remoteChannel] = await pairPromise; - localDataChannel.send("test"); + localChannel.send("test"); await new Promise(resolve => { - remoteDataChannel.onmessage = resolve; + remoteChannel.onmessage = resolve; }); const remoteCandidateStats = [...(await localPc.getStats()).values()].find(({type}) => type === 'remote-candidate');