Commit 524a99e5 authored by Francois Doray's avatar Francois Doray Committed by Commit Bot

Migrate multi-threaded cert verifier from WorkerPool to TaskScheduler.

WorkerPool is being deprecated in favor of TaskScheduler.

NSS functions may reenter //net via extension hooks. If the reentered
code needs to synchronously wait for a task to run but the thread pool
in which that task must run doesn't have enough threads to schedule it,
a deadlock occurs. To prevent that, calls to NSS functions are made
within the scope of a MAY_BLOCK base::ScopedBlockingCall which
increments the thread pool capacity after a short timeout.

Bug: 659191
Change-Id: I2a2719f7c104f1ecf8cac7e4019836920c777261
Reviewed-on: https://chromium-review.googlesource.com/648328
Commit-Queue: Francois Doray <fdoray@chromium.org>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Reviewed-by: default avatarRyan Sleevi <rsleevi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#504439}
parent 61373a39
...@@ -72,6 +72,7 @@ class GpuState; ...@@ -72,6 +72,7 @@ class GpuState;
} }
namespace net { namespace net {
class NetworkChangeNotifierMac; class NetworkChangeNotifierMac;
class OSCPScopedAllowBaseSyncPrimitives;
namespace internal { namespace internal {
class AddressTrackerLinux; class AddressTrackerLinux;
} }
...@@ -208,6 +209,7 @@ class BASE_EXPORT ScopedAllowBaseSyncPrimitives { ...@@ -208,6 +209,7 @@ class BASE_EXPORT ScopedAllowBaseSyncPrimitives {
ScopedAllowBaseSyncPrimitivesResetsState); ScopedAllowBaseSyncPrimitivesResetsState);
FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest, FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
ScopedAllowBaseSyncPrimitivesWithBlockingDisallowed); ScopedAllowBaseSyncPrimitivesWithBlockingDisallowed);
friend class net::OSCPScopedAllowBaseSyncPrimitives;
ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF; ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
~ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF; ~ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/sha1.h" #include "base/sha1.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "crypto/sha2.h" #include "crypto/sha2.h"
...@@ -449,6 +450,14 @@ int CertVerifyProc::Verify(X509Certificate* cert, ...@@ -449,6 +450,14 @@ int CertVerifyProc::Verify(X509Certificate* cert,
CRLSet* crl_set, CRLSet* crl_set,
const CertificateList& additional_trust_anchors, const CertificateList& additional_trust_anchors,
CertVerifyResult* verify_result) { CertVerifyResult* verify_result) {
// CertVerifyProc's contract allows ::VerifyInternal() to wait on File I/O
// (such as the Windows registry or smart cards on all platforms) or may re-
// enter this code via extension hooks (such as smart card UI). To ensure
// threads are not starved or deadlocked, the base::ScopedBlockingCall below
// increments the thread pool capacity when this method takes too much time to
// run.
base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
verify_result->Reset(); verify_result->Reset();
verify_result->verified_cert = cert; verify_result->verified_cert = cert;
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/sha1.h" #include "base/sha1.h"
#include "base/threading/worker_pool.h" #include "base/task_scheduler/post_task.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "base/values.h" #include "base/values.h"
...@@ -53,7 +53,7 @@ class NetLogCaptureMode; ...@@ -53,7 +53,7 @@ class NetLogCaptureMode;
// fundamentally doing the same verification. CertVerifierJob is similarly // fundamentally doing the same verification. CertVerifierJob is similarly
// thread-unsafe and lives on the origin thread. // thread-unsafe and lives on the origin thread.
// //
// To do the actual work, CertVerifierJob posts a task to WorkerPool // To do the actual work, CertVerifierJob posts a task to TaskScheduler
// (PostTaskAndReply), and on completion notifies all requests attached to it. // (PostTaskAndReply), and on completion notifies all requests attached to it.
// //
// Cancellation: // Cancellation:
...@@ -107,9 +107,8 @@ std::unique_ptr<base::Value> CertVerifyResultCallback( ...@@ -107,9 +107,8 @@ std::unique_ptr<base::Value> CertVerifyResultCallback(
return std::move(results); return std::move(results);
} }
// Helper structure used to keep |verify_result| alive for the lifetime of // Used to pass the result of CertVerifierJob::DoVerifyOnWorkerThread() to
// the verification on the worker thread, and to communicate it back to the // CertVerifierJob::OnJobCompleted().
// calling thread.
struct ResultHelper { struct ResultHelper {
int error; int error;
CertVerifyResult result; CertVerifyResult result;
...@@ -175,19 +174,21 @@ class CertVerifierRequest : public base::LinkNode<CertVerifierRequest>, ...@@ -175,19 +174,21 @@ class CertVerifierRequest : public base::LinkNode<CertVerifierRequest>,
}; };
// DoVerifyOnWorkerThread runs the verification synchronously on a worker // DoVerifyOnWorkerThread runs the verification synchronously on a worker
// thread. The output parameters (error and result) must remain alive. // thread.
void DoVerifyOnWorkerThread(const scoped_refptr<CertVerifyProc>& verify_proc, std::unique_ptr<ResultHelper> DoVerifyOnWorkerThread(
const scoped_refptr<X509Certificate>& cert, const scoped_refptr<CertVerifyProc>& verify_proc,
const std::string& hostname, const scoped_refptr<X509Certificate>& cert,
const std::string& ocsp_response, const std::string& hostname,
int flags, const std::string& ocsp_response,
const scoped_refptr<CRLSet>& crl_set, int flags,
const CertificateList& additional_trust_anchors, const scoped_refptr<CRLSet>& crl_set,
int* error, const CertificateList& additional_trust_anchors) {
CertVerifyResult* result) {
TRACE_EVENT0(kNetTracingCategory, "DoVerifyOnWorkerThread"); TRACE_EVENT0(kNetTracingCategory, "DoVerifyOnWorkerThread");
*error = verify_proc->Verify(cert.get(), hostname, ocsp_response, flags, auto verify_result = std::make_unique<ResultHelper>();
crl_set.get(), additional_trust_anchors, result); verify_result->error = verify_proc->Verify(
cert.get(), hostname, ocsp_response, flags, crl_set.get(),
additional_trust_anchors, &verify_result->result);
return verify_result;
} }
// CertVerifierJob lives only on the verifier's origin message loop. // CertVerifierJob lives only on the verifier's origin message loop.
...@@ -214,27 +215,18 @@ class CertVerifierJob { ...@@ -214,27 +215,18 @@ class CertVerifierJob {
const CertVerifier::RequestParams& key() const { return key_; } const CertVerifier::RequestParams& key() const { return key_; }
// Posts a task to the worker pool to do the verification. Once the // Posts a task to TaskScheduler to do the verification. Once the verification
// verification has completed on the worker thread, it will call // has completed, it will call OnJobCompleted() on the origin thread.
// OnJobCompleted() on the origin thread. void Start(const scoped_refptr<CertVerifyProc>& verify_proc,
bool Start(const scoped_refptr<CertVerifyProc>& verify_proc,
const scoped_refptr<CRLSet>& crl_set) { const scoped_refptr<CRLSet>& crl_set) {
// Owned by the bound reply callback. base::PostTaskWithTraitsAndReplyWithResult(
std::unique_ptr<ResultHelper> owned_result(new ResultHelper());
// Parameter evaluation order is undefined in C++. Ensure the pointer value
// is gotten before calling base::Passed().
auto* result = owned_result.get();
return base::WorkerPool::PostTaskAndReply(
FROM_HERE, FROM_HERE,
base::Bind(&DoVerifyOnWorkerThread, verify_proc, key_.certificate(), {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
key_.hostname(), key_.ocsp_response(), key_.flags(), crl_set, base::BindOnce(&DoVerifyOnWorkerThread, verify_proc, key_.certificate(),
key_.additional_trust_anchors(), &result->error, key_.hostname(), key_.ocsp_response(), key_.flags(),
&result->result), crl_set, key_.additional_trust_anchors()),
base::Bind(&CertVerifierJob::OnJobCompleted, base::BindOnce(&CertVerifierJob::OnJobCompleted,
weak_ptr_factory_.GetWeakPtr(), base::Passed(&owned_result)), weak_ptr_factory_.GetWeakPtr()));
true /* task is slow */);
} }
~CertVerifierJob() { ~CertVerifierJob() {
...@@ -358,11 +350,7 @@ int MultiThreadedCertVerifier::Verify(const RequestParams& params, ...@@ -358,11 +350,7 @@ int MultiThreadedCertVerifier::Verify(const RequestParams& params,
std::unique_ptr<CertVerifierJob> new_job = std::unique_ptr<CertVerifierJob> new_job =
std::make_unique<CertVerifierJob>(params, net_log.net_log(), this); std::make_unique<CertVerifierJob>(params, net_log.net_log(), this);
if (!new_job->Start(verify_proc_, crl_set)) { new_job->Start(verify_proc_, crl_set);
// TODO(wtc): log to the NetLog.
LOG(ERROR) << "CertVerifierJob couldn't be started.";
return ERR_INSUFFICIENT_RESOURCES; // Just a guess.
}
job = new_job.get(); job = new_job.get();
inflight_[job] = std::move(new_job); inflight_[job] = std::move(new_job);
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "base/synchronization/condition_variable.h" #include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "net/base/elements_upload_data_stream.h" #include "net/base/elements_upload_data_stream.h"
#include "net/base/host_port_pair.h" #include "net/base/host_port_pair.h"
...@@ -49,6 +50,13 @@ ...@@ -49,6 +50,13 @@
namespace net { namespace net {
// OSCPScopedAllowBaseSyncPrimitives is a friend and derived class of
// base::ScopedAllowBaseSyncPrimitives which can be instantiated by
// OCSPRequestSession. OCSPRequestSession can't itself be a friend of
// base::ScopedAllowBaseSyncPrimitives because it is in the anonymous namespace.
class OSCPScopedAllowBaseSyncPrimitives
: public base::ScopedAllowBaseSyncPrimitives {};
namespace { namespace {
// Protects |g_request_context|. // Protects |g_request_context|.
...@@ -240,6 +248,9 @@ class OCSPRequestSession ...@@ -240,6 +248,9 @@ class OCSPRequestSession
} }
bool Wait() { bool Wait() {
// This method waits on a ConditionVariable from a base::MayBlock task.
OSCPScopedAllowBaseSyncPrimitives scoped_allow_base_sync_primitives;
base::TimeDelta timeout = timeout_; base::TimeDelta timeout = timeout_;
base::AutoLock autolock(lock_); base::AutoLock autolock(lock_);
while (!finished_) { while (!finished_) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment