diff options
author | Eric Roman <eroman@chromium.org> | 2020-08-13 01:28:25 +0000 |
---|---|---|
committer | Michael BrĂ¼ning <michael.bruning@qt.io> | 2020-10-27 11:23:44 +0000 |
commit | 327474aed0ed642963de170a422b2288c32f7a2d (patch) | |
tree | 3c182058c7f497fa03bbb419f350ba7a4a3e2a19 | |
parent | 9d173d02d5ec0a2a3e3051ced579d0507d5bcf54 (diff) | |
download | qtwebengine-chromium-327474aed0ed642963de170a422b2288c32f7a2d.tar.gz |
[Backport] CVE-2020-6557: Inappropriate implementation in networking
Manual backport of patch originally reviewed on
https://chromium-review.googlesource.com/c/chromium/src/+/2350395:
Add a per-process limit on open UDP sockets.
This adds a default limit of 6000 on the open UDP sockets throughout
the entire process, configurable with the "LimitOpenUDPSockets" feature.
An "open UDP socket" specifically means a net::UDPSocket which
successfully called Open(), and has not yet called Close().
Once the limit has been reached, opening UDP socket will fail with
ERR_INSUFFICIENT_RESOURCES.
In Chrome Browser, UDP sockets are brokered through a single process
(that hosting the Network Service), so this is functionally a
browser-wide limit too.
Bug: 1083278
Change-Id: Ib95ab14b7ccf5e15410b9df9537c66c858de2d7d
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r-- | chromium/net/BUILD.gn | 2 | ||||
-rw-r--r-- | chromium/net/base/features.cc | 7 | ||||
-rw-r--r-- | chromium/net/base/features.h | 8 | ||||
-rw-r--r-- | chromium/net/socket/udp_socket_global_limits.cc | 92 | ||||
-rw-r--r-- | chromium/net/socket/udp_socket_global_limits.h | 75 | ||||
-rw-r--r-- | chromium/net/socket/udp_socket_posix.cc | 8 | ||||
-rw-r--r-- | chromium/net/socket/udp_socket_posix.h | 5 | ||||
-rw-r--r-- | chromium/net/socket/udp_socket_win.cc | 8 | ||||
-rw-r--r-- | chromium/net/socket/udp_socket_win.h | 5 |
9 files changed, 210 insertions, 0 deletions
diff --git a/chromium/net/BUILD.gn b/chromium/net/BUILD.gn index dd1fe1f4a35..f5cecb083af 100644 --- a/chromium/net/BUILD.gn +++ b/chromium/net/BUILD.gn @@ -1008,6 +1008,8 @@ component("net") { "socket/udp_server_socket.cc", "socket/udp_server_socket.h", "socket/udp_socket.h", + "socket/udp_socket_global_limits.cc", + "socket/udp_socket_global_limits.h", "socket/websocket_endpoint_lock_manager.cc", "socket/websocket_endpoint_lock_manager.h", "socket/websocket_transport_client_socket_pool.cc", diff --git a/chromium/net/base/features.cc b/chromium/net/base/features.cc index 46b4562c621..949b75c15ce 100644 --- a/chromium/net/base/features.cc +++ b/chromium/net/base/features.cc @@ -119,5 +119,12 @@ const base::Feature kSchemefulSameSite{"SchemefulSameSite", const base::Feature kTLSLegacyCryptoFallbackForMetrics{ "TLSLegacyCryptoFallbackForMetrics", base::FEATURE_ENABLED_BY_DEFAULT}; +const base::Feature kLimitOpenUDPSockets{"LimitOpenUDPSockets", + base::FEATURE_ENABLED_BY_DEFAULT}; +extern const base::FeatureParam<int> kLimitOpenUDPSocketsMax( + &kLimitOpenUDPSockets, + "LimitOpenUDPSocketsMax", + 6000); + } // namespace features } // namespace net diff --git a/chromium/net/base/features.h b/chromium/net/base/features.h index 2214b6541c9..5ed095159ad 100644 --- a/chromium/net/base/features.h +++ b/chromium/net/base/features.h @@ -171,6 +171,14 @@ NET_EXPORT extern const base::Feature kSchemefulSameSite; // those algorithms. If disabled, the algorithms will always be offered. NET_EXPORT extern const base::Feature kTLSLegacyCryptoFallbackForMetrics; +// Enables a process-wide limit on "open" UDP sockets. See +// udp_socket_global_limits.h for details on what constitutes an "open" socket. +NET_EXPORT extern const base::Feature kLimitOpenUDPSockets; +// FeatureParams associated with kLimitOpenUDPSockets. +// Sets the maximum allowed open UDP sockets. Provisioning more sockets than +// this will result in a failure (ERR_INSUFFICIENT_RESOURCES). +NET_EXPORT extern const base::FeatureParam<int> kLimitOpenUDPSocketsMax; + } // namespace features } // namespace net diff --git a/chromium/net/socket/udp_socket_global_limits.cc b/chromium/net/socket/udp_socket_global_limits.cc new file mode 100644 index 00000000000..21cd7a5cdb1 --- /dev/null +++ b/chromium/net/socket/udp_socket_global_limits.cc @@ -0,0 +1,92 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <limits> + +#include "base/atomic_ref_count.h" +#include "base/no_destructor.h" +#include "net/base/features.h" +#include "net/socket/udp_socket_global_limits.h" + +namespace net { + +namespace { + +// Threadsafe singleton for tracking the process-wide count of UDP sockets. +class GlobalUDPSocketCounts { + public: + GlobalUDPSocketCounts() : count_(0) {} + + ~GlobalUDPSocketCounts() = delete; + + static GlobalUDPSocketCounts& Get() { + static base::NoDestructor<GlobalUDPSocketCounts> singleton; + return *singleton; + } + + bool TryAcquireSocket() WARN_UNUSED_RESULT { + int previous = count_.Increment(1); + if (previous >= GetMax()) { + count_.Increment(-1); + return false; + } + + return true; + } + + int GetMax() { + if (base::FeatureList::IsEnabled(features::kLimitOpenUDPSockets)) + return features::kLimitOpenUDPSocketsMax.Get(); + + return std::numeric_limits<int>::max(); + } + + void ReleaseSocket() { count_.Increment(-1); } + + int GetCountForTesting() { return count_.SubtleRefCountForDebug(); } + + private: + base::AtomicRefCount count_; +}; + +} // namespace + +OwnedUDPSocketCount::OwnedUDPSocketCount() : OwnedUDPSocketCount(true) {} + +OwnedUDPSocketCount::OwnedUDPSocketCount(OwnedUDPSocketCount&& other) { + *this = std::move(other); +} + +OwnedUDPSocketCount& OwnedUDPSocketCount::operator=( + OwnedUDPSocketCount&& other) { + Reset(); + empty_ = other.empty_; + other.empty_ = true; + return *this; +} + +OwnedUDPSocketCount::~OwnedUDPSocketCount() { + Reset(); +} + +void OwnedUDPSocketCount::Reset() { + if (!empty_) { + GlobalUDPSocketCounts::Get().ReleaseSocket(); + empty_ = true; + } +} + +OwnedUDPSocketCount::OwnedUDPSocketCount(bool empty) : empty_(empty) {} + +OwnedUDPSocketCount TryAcquireGlobalUDPSocketCount() { + bool success = GlobalUDPSocketCounts::Get().TryAcquireSocket(); + return OwnedUDPSocketCount(!success); +} + +int GetGlobalUDPSocketCountForTesting() { + return GlobalUDPSocketCounts::Get().GetCountForTesting(); +} + +} // namespace net + diff --git a/chromium/net/socket/udp_socket_global_limits.h b/chromium/net/socket/udp_socket_global_limits.h new file mode 100644 index 00000000000..e6a4c54d0f2 --- /dev/null +++ b/chromium/net/socket/udp_socket_global_limits.h @@ -0,0 +1,75 @@ +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SOCKET_UDP_SOCKET_GLOBAL_LIMITS_H_ +#define NET_SOCKET_UDP_SOCKET_GLOBAL_LIMITS_H_ + +#include "base/compiler_specific.h" +#include "net/base/net_errors.h" +#include "net/base/net_export.h" + +namespace net { + +// Helper class for RAII-style management of the global count of "open UDP +// sockets" [1] in the process. +// +// Keeping OwnedUDPSocketCount alive increases the global socket counter by 1. +// When it goes out of scope - or is explicitly Reset() - the reference is +// returned to the global counter. +class NET_EXPORT OwnedUDPSocketCount { + public: + // The default constructor builds an empty OwnedUDPSocketCount (does not own a + // count). + OwnedUDPSocketCount(); + + // Any count held by OwnedUDPSocketCount is transferred when moving. + OwnedUDPSocketCount(OwnedUDPSocketCount&&); + OwnedUDPSocketCount& operator=(OwnedUDPSocketCount&&); + + // This is a move-only type. + OwnedUDPSocketCount(const OwnedUDPSocketCount&) = delete; + OwnedUDPSocketCount& operator=(const OwnedUDPSocketCount&) = delete; + + ~OwnedUDPSocketCount(); + + // Returns false if this instance "owns" a socket count. In + // other words, when |empty()|, destruction of |this| will + // not change the global socket count. + bool empty() const { return empty_; } + + // Resets |this| to an empty state (|empty()| becomes true after + // calling this). If |this| was previously |!empty()|, the global + // socket count will be decremented. + void Reset(); + + private: + // Only TryAcquireGlobalUDPSocketCount() is allowed to construct a non-empty + // OwnedUDPSocketCount. + friend NET_EXPORT OwnedUDPSocketCount TryAcquireGlobalUDPSocketCount(); + explicit OwnedUDPSocketCount(bool empty); + + bool empty_; +}; + +// Attempts to increase the global "open UDP socket" [1] count. +// +// * On failure returns an OwnedUDPSocketCount that is |empty()|. This happens +// if the global socket limit has been reached. +// * On success returns an OwnedUDPSocketCount that is |!empty()|. This +// OwnedUDPSocketCount should be kept alive until the socket resource is +// released. +// +// [1] For simplicity, an "open UDP socket" is defined as a net::UDPSocket that +// successfully called Open(), and has not yet called Close(). This is +// analogous to the number of open platform socket handles, and in practice +// should also be a good proxy for the number of consumed UDP ports. +NET_EXPORT OwnedUDPSocketCount TryAcquireGlobalUDPSocketCount() + WARN_UNUSED_RESULT; + +// Returns the current count of open UDP sockets (for testing only). +NET_EXPORT int GetGlobalUDPSocketCountForTesting(); + +} // namespace net + +#endif // NET_SOCKET_UDP_SOCKET_GLOBAL_LIMITS_H_ + diff --git a/chromium/net/socket/udp_socket_posix.cc b/chromium/net/socket/udp_socket_posix.cc index 0b61ca9bac2..7df6892d67e 100644 --- a/chromium/net/socket/udp_socket_posix.cc +++ b/chromium/net/socket/udp_socket_posix.cc @@ -215,6 +215,10 @@ int UDPSocketPosix::Open(AddressFamily address_family) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_EQ(socket_, kInvalidSocket); + auto owned_socket_count = TryAcquireGlobalUDPSocketCount(); + if (owned_socket_count.empty()) + return ERR_INSUFFICIENT_RESOURCES; + addr_family_ = ConvertAddressFamily(address_family); socket_ = CreatePlatformSocket(addr_family_, SOCK_DGRAM, 0); if (socket_ == kInvalidSocket) @@ -231,6 +235,8 @@ int UDPSocketPosix::Open(AddressFamily address_family) { } if (tag_ != SocketTag()) tag_.Apply(socket_); + + owned_socket_count_ = std::move(owned_socket_count); return OK; } @@ -292,6 +298,8 @@ void UDPSocketPosix::ReceivedActivityMonitor::NetworkActivityMonitorIncrement( void UDPSocketPosix::Close() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + owned_socket_count_.Reset(); + if (socket_ == kInvalidSocket) return; diff --git a/chromium/net/socket/udp_socket_posix.h b/chromium/net/socket/udp_socket_posix.h index ce96046e720..06b6131adad 100644 --- a/chromium/net/socket/udp_socket_posix.h +++ b/chromium/net/socket/udp_socket_posix.h @@ -29,6 +29,7 @@ #include "net/socket/diff_serv_code_point.h" #include "net/socket/socket_descriptor.h" #include "net/socket/socket_tag.h" +#include "net/socket/udp_socket_global_limits.h" #include "net/traffic_annotation/network_traffic_annotation.h" #if defined(__ANDROID__) && defined(__aarch64__) @@ -629,6 +630,10 @@ class NET_EXPORT UDPSocketPosix { // enable_experimental_recv_optimization() method. bool experimental_recv_optimization_enabled_; + // Manages decrementing the global open UDP socket counter when this + // UDPSocket is destroyed. + OwnedUDPSocketCount owned_socket_count_; + THREAD_CHECKER(thread_checker_); // Used for alternate writes that are posted for concurrent execution. diff --git a/chromium/net/socket/udp_socket_win.cc b/chromium/net/socket/udp_socket_win.cc index 0d080437839..587459b5f4c 100644 --- a/chromium/net/socket/udp_socket_win.cc +++ b/chromium/net/socket/udp_socket_win.cc @@ -272,6 +272,10 @@ int UDPSocketWin::Open(AddressFamily address_family) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_EQ(socket_, INVALID_SOCKET); + auto owned_socket_count = TryAcquireGlobalUDPSocketCount(); + if (owned_socket_count.empty()) + return ERR_INSUFFICIENT_RESOURCES; + addr_family_ = ConvertAddressFamily(address_family); socket_ = CreatePlatformSocket(addr_family_, SOCK_DGRAM, IPPROTO_UDP); if (socket_ == INVALID_SOCKET) @@ -282,12 +286,16 @@ int UDPSocketWin::Open(AddressFamily address_family) { read_write_event_.Set(WSACreateEvent()); WSAEventSelect(socket_, read_write_event_.Get(), FD_READ | FD_WRITE); } + + owned_socket_count_ = std::move(owned_socket_count); return OK; } void UDPSocketWin::Close() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + owned_socket_count_.Reset(); + if (socket_ == INVALID_SOCKET) return; diff --git a/chromium/net/socket/udp_socket_win.h b/chromium/net/socket/udp_socket_win.h index 380ff03298c..a11d07a32ea 100644 --- a/chromium/net/socket/udp_socket_win.h +++ b/chromium/net/socket/udp_socket_win.h @@ -28,6 +28,7 @@ #include "net/log/net_log_with_source.h" #include "net/socket/datagram_socket.h" #include "net/socket/diff_serv_code_point.h" +#include "net/socket/udp_socket_global_limits.h" #include "net/traffic_annotation/network_traffic_annotation.h" namespace net { @@ -485,6 +486,10 @@ class NET_EXPORT UDPSocketWin : public base::win::ObjectWatcher::Delegate { // Maintains remote addresses for QWAVE qos management. std::unique_ptr<DscpManager> dscp_manager_; + // Manages decrementing the global open UDP socket counter when this + // UDPSocket is destroyed. + OwnedUDPSocketCount owned_socket_count_; + THREAD_CHECKER(thread_checker_); // Used to prevent null dereferences in OnObjectSignaled, when passing an |