device/fido/mac: fix a threading bug in TouchIdContext

TouchIdContext implements the Touch ID authorization prompt. After
receiving the OS reply callback from the Security framework it forwards
the result to the user-supplied callback, but doesn't do so on the
original TaskRunner that it was invoked on. As a result, Touch ID
authenticator requests fail some sequence checker tests. This fixes that
by posting the user-supplied callback back on the original runner.

Bug: 678128
Change-Id: I0e7a8730ec898f288cd25323134aa95ef50ddbdf
Reviewed-on: https://chromium-review.googlesource.com/1136496
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Reviewed-by: Kim Paulhamus <kpaulhamus@chromium.org>
Cr-Commit-Position: refs/heads/master@{#575361}
diff --git a/device/fido/mac/get_assertion_operation.h b/device/fido/mac/get_assertion_operation.h
index 3b4efd5..3146c8f 100644
--- a/device/fido/mac/get_assertion_operation.h
+++ b/device/fido/mac/get_assertion_operation.h
@@ -40,7 +40,7 @@
 
  private:
   const std::string& RpId() const override;
-  void PromptTouchIdDone(bool success, NSError* err) override;
+  void PromptTouchIdDone(bool success) override;
 
   DISALLOW_COPY_AND_ASSIGN(GetAssertionOperation);
 };
diff --git a/device/fido/mac/get_assertion_operation.mm b/device/fido/mac/get_assertion_operation.mm
index e778cd1..4f1bec1 100644
--- a/device/fido/mac/get_assertion_operation.mm
+++ b/device/fido/mac/get_assertion_operation.mm
@@ -52,11 +52,8 @@
   PromptTouchId("sign in to " + RpId());
 }
 
-void GetAssertionOperation::PromptTouchIdDone(bool success, NSError* err) {
+void GetAssertionOperation::PromptTouchIdDone(bool success) {
   if (!success) {
-    // err is autoreleased.
-    CHECK(err != nil);
-    DVLOG(1) << "Touch ID prompt failed: " << base::mac::NSToCFCast(err);
     std::move(callback())
         .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt);
     return;
diff --git a/device/fido/mac/make_credential_operation.h b/device/fido/mac/make_credential_operation.h
index f30ce14..4f9f78c 100644
--- a/device/fido/mac/make_credential_operation.h
+++ b/device/fido/mac/make_credential_operation.h
@@ -58,7 +58,7 @@
  private:
   // OperationBase:
   const std::string& RpId() const override;
-  void PromptTouchIdDone(bool success, NSError* err) override;
+  void PromptTouchIdDone(bool success) override;
 
   base::Optional<std::vector<uint8_t>> GenerateCredentialIdForRequest() const;
 };
diff --git a/device/fido/mac/make_credential_operation.mm b/device/fido/mac/make_credential_operation.mm
index 0081e96..51f598dd 100644
--- a/device/fido/mac/make_credential_operation.mm
+++ b/device/fido/mac/make_credential_operation.mm
@@ -70,11 +70,8 @@
   PromptTouchId("register with " + RpId());
 }
 
-void MakeCredentialOperation::PromptTouchIdDone(bool success, NSError* err) {
+void MakeCredentialOperation::PromptTouchIdDone(bool success) {
   if (!success) {
-    // err is autoreleased.
-    CHECK(err != nil);
-    DVLOG(1) << "Touch ID prompt failed: " << base::mac::NSToCFCast(err);
     std::move(callback())
         .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt);
     return;
diff --git a/device/fido/mac/operation_base.h b/device/fido/mac/operation_base.h
index 6f030be..5452520 100644
--- a/device/fido/mac/operation_base.h
+++ b/device/fido/mac/operation_base.h
@@ -65,8 +65,8 @@
                                           base::Unretained(this)));
   }
 
-  // Callback for |PromptTouchId|. Any NSError that gets passed is autoreleased.
-  virtual void PromptTouchIdDone(bool success, NSError* err) = 0;
+  // Callback for |PromptTouchId|.
+  virtual void PromptTouchIdDone(bool success) = 0;
 
   // Subclasses override RpId to return the RP ID from the type-specific
   // request.
diff --git a/device/fido/mac/touch_id_context.h b/device/fido/mac/touch_id_context.h
index 29133b4..e54f64d 100644
--- a/device/fido/mac/touch_id_context.h
+++ b/device/fido/mac/touch_id_context.h
@@ -23,9 +23,8 @@
 class API_AVAILABLE(macosx(10.12.2)) TouchIdContext {
  public:
   // The callback is invoked when the Touch ID prompt completes. It receives a
-  // boolean indicating success and an autoreleased NSError if the prompt was
-  // denied or failed.
-  using Callback = base::OnceCallback<void(bool, NSError*)>;
+  // boolean indicating whether obtaining the fingerprint was successful.
+  using Callback = base::OnceCallback<void(bool)>;
 
   TouchIdContext();
   ~TouchIdContext();
@@ -44,6 +43,8 @@
   SecAccessControlRef access_control() const { return access_control_; }
 
  private:
+  void RunCallback(bool success);
+
   base::scoped_nsobject<LAContext> context_;
   base::ScopedCFTypeRef<SecAccessControlRef> access_control_;
   Callback callback_;
diff --git a/device/fido/mac/touch_id_context.mm b/device/fido/mac/touch_id_context.mm
index 8a31f2bc..f372a44f 100644
--- a/device/fido/mac/touch_id_context.mm
+++ b/device/fido/mac/touch_id_context.mm
@@ -6,7 +6,12 @@
 
 #import <Foundation/Foundation.h>
 
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/sequenced_task_runner.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace device {
 namespace fido {
@@ -33,6 +38,8 @@
 
 void TouchIdContext::PromptTouchId(std::string reason, Callback callback) {
   callback_ = std::move(callback);
+  scoped_refptr<base::SequencedTaskRunner> runner =
+      base::SequencedTaskRunnerHandle::Get();
   auto weak_self = weak_ptr_factory_.GetWeakPtr();
   // If evaluation succeeds (i.e. user provides a fingerprint), |context_| can
   // be used for one signing operation. N.B. even in |MakeCredentialOperation|,
@@ -42,13 +49,35 @@
                         operation:LAAccessControlOperationUseKeySign
                   localizedReason:base::SysUTF8ToNSString(reason)
                             reply:^(BOOL success, NSError* error) {
-                              if (!weak_self) {
-                                return;
+                              // The reply block is invoked in a separate
+                              // thread. We want to invoke the callback in the
+                              // original thread, so we post it onto the
+                              // originating runner. The weak_self and runner
+                              // variables inside the block are const-copies of
+                              // the ones in the enclosing scope (c.f.
+                              // http://clang.llvm.org/docs/Block-ABI-Apple.html#imported-variables).
+                              if (!success) {
+                                // |error| is autoreleased in this block. It
+                                // is not currently passed onto the other
+                                // thread running the callback; but if it
+                                // were, it would have to be retained first
+                                // (and probably captured in a
+                                // scoped_nsobject<NSError>).
+                                DCHECK(error != nil);
+                                DVLOG(1) << "Touch ID prompt failed: "
+                                         << base::mac::NSToCFCast(error);
                               }
-                              std::move(callback_).Run(success, error);
+                              runner->PostTask(
+                                  FROM_HERE,
+                                  base::BindOnce(&TouchIdContext::RunCallback,
+                                                 weak_self, success));
                             }];
 }
 
+void TouchIdContext::RunCallback(bool success) {
+  std::move(callback_).Run(success);
+}
+
 }  // namespace mac
 }  // namespace fido
 }  // namespace device