Content Verification: Don't access UI-thread objects on the IO thread

We ask to create ContentVerifyJob's on the IO thread as we read files
from extension directories, but we are accidentally accessing state that
lives on the UI thread (eg the ExtensionRegistry) while deciding whether
to do that or not.

This changes things so that as we discover extensions loading up, we
copy the data we need to make those decisions to an object on the IO
thread.

Also fix a bug that was keeping BOOTSTRAP mode from working, and one or
two other minor issues I found while I was staring at things.

BUG=395873,392216

Review URL: https://codereview.chromium.org/407043002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284804 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_system_impl.cc b/chrome/browser/extensions/extension_system_impl.cc
index ee61be9..771524e 100644
--- a/chrome/browser/extensions/extension_system_impl.cc
+++ b/chrome/browser/extensions/extension_system_impl.cc
@@ -222,7 +222,15 @@
   }
 
   virtual void VerifyFailed(const std::string& extension_id) OVERRIDE {
-    if (service_)
+    if (!service_)
+      return;
+    ExtensionRegistry* registry = ExtensionRegistry::Get(service_->profile());
+    const Extension* extension =
+        registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+    if (!extension)
+      return;
+    Mode mode = ShouldBeVerified(*extension);
+    if (mode >= ContentVerifierDelegate::ENFORCE)
       service_->DisableExtension(extension_id, Extension::DISABLE_CORRUPTED);
   }
 
@@ -324,7 +332,7 @@
 #if defined(OS_CHROMEOS)
     mode = std::max(mode, ContentVerifierDelegate::BOOTSTRAP);
 #endif
-    if (mode > ContentVerifierDelegate::BOOTSTRAP)
+    if (mode >= ContentVerifierDelegate::BOOTSTRAP)
       content_verifier_->Start();
     info_map()->SetContentVerifier(content_verifier_.get());
 
diff --git a/chrome/browser/extensions/user_script_master.cc b/chrome/browser/extensions/user_script_master.cc
index e5ac73b..a3e5846 100644
--- a/chrome/browser/extensions/user_script_master.cc
+++ b/chrome/browser/extensions/user_script_master.cc
@@ -44,6 +44,7 @@
                    const base::FilePath& extension_root,
                    const base::FilePath& relative_path,
                    const std::string& content) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   scoped_refptr<ContentVerifyJob> job(
       verifier->CreateJobFor(extension_id, extension_root, relative_path));
   if (job.get()) {
@@ -81,11 +82,14 @@
       return false;
     }
     if (verifier) {
-      VerifyContent(verifier,
-                    extension_id,
-                    script_file->extension_root(),
-                    script_file->relative_path(),
-                    content);
+      content::BrowserThread::PostTask(content::BrowserThread::IO,
+                                       FROM_HERE,
+                                       base::Bind(&VerifyContent,
+                                                  verifier,
+                                                  extension_id,
+                                                  script_file->extension_root(),
+                                                  script_file->relative_path(),
+                                                  content));
     }
   }
 
diff --git a/extensions/browser/content_hash_fetcher.cc b/extensions/browser/content_hash_fetcher.cc
index 041393e..68b0c92 100644
--- a/extensions/browser/content_hash_fetcher.cc
+++ b/extensions/browser/content_hash_fetcher.cc
@@ -23,6 +23,7 @@
 #include "crypto/sha2.h"
 #include "extensions/browser/computed_hashes.h"
 #include "extensions/browser/content_hash_tree.h"
+#include "extensions/browser/content_verifier_delegate.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/verified_contents.h"
 #include "extensions/common/constants.h"
@@ -51,7 +52,7 @@
  public:
   typedef base::Callback<void(ContentHashFetcherJob*)> CompletionCallback;
   ContentHashFetcherJob(net::URLRequestContextGetter* request_context,
-                        ContentVerifierKey key,
+                        const ContentVerifierKey& key,
                         const std::string& extension_id,
                         const base::FilePath& extension_path,
                         const GURL& fetch_url,
@@ -156,7 +157,7 @@
 
 ContentHashFetcherJob::ContentHashFetcherJob(
     net::URLRequestContextGetter* request_context,
-    ContentVerifierKey key,
+    const ContentVerifierKey& key,
     const std::string& extension_id,
     const base::FilePath& extension_path,
     const GURL& fetch_url,
@@ -419,7 +420,6 @@
     : context_(context),
       delegate_(delegate),
       fetch_callback_(callback),
-      observer_(this),
       weak_ptr_factory_(this) {
 }
 
@@ -429,11 +429,6 @@
   }
 }
 
-void ContentHashFetcher::Start() {
-  ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
-  observer_.Add(registry);
-}
-
 void ContentHashFetcher::DoFetch(const Extension* extension, bool force) {
   DCHECK(extension);
 
@@ -471,17 +466,12 @@
   job->Start();
 }
 
-void ContentHashFetcher::OnExtensionLoaded(
-    content::BrowserContext* browser_context,
-    const Extension* extension) {
+void ContentHashFetcher::ExtensionLoaded(const Extension* extension) {
   CHECK(extension);
   DoFetch(extension, false);
 }
 
-void ContentHashFetcher::OnExtensionUnloaded(
-    content::BrowserContext* browser_context,
-    const Extension* extension,
-    UnloadedExtensionInfo::Reason reason) {
+void ContentHashFetcher::ExtensionUnloaded(const Extension* extension) {
   CHECK(extension);
   IdAndVersion key(extension->id(), extension->version()->GetString());
   JobMap::iterator found = jobs_.find(key);
diff --git a/extensions/browser/content_hash_fetcher.h b/extensions/browser/content_hash_fetcher.h
index 55c845f..7919691 100644
--- a/extensions/browser/content_hash_fetcher.h
+++ b/extensions/browser/content_hash_fetcher.h
@@ -11,9 +11,6 @@
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
-#include "extensions/browser/content_verifier_delegate.h"
-#include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension.h"
 
 namespace content {
@@ -24,6 +21,7 @@
 
 class ExtensionRegistry;
 class ContentHashFetcherJob;
+class ContentVerifierDelegate;
 
 // This class is responsible for getting signed expected hashes for use in
 // extension content verification. As extensions are loaded it will fetch and
@@ -31,7 +29,7 @@
 // hashes for each block of each file within an extension. (These unsigned leaf
 // node block level hashes will always be checked at time of use use to make
 // sure they match the signed treehash root hash).
-class ContentHashFetcher : public ExtensionRegistryObserver {
+class ContentHashFetcher {
  public:
   // A callback for when a fetch is complete. This reports back:
   // -extension id
@@ -50,22 +48,14 @@
                      const FetchCallback& callback);
   virtual ~ContentHashFetcher();
 
-  // Begins the process of trying to fetch any needed verified contents, and
-  // listening for extension load/unload.
-  void Start();
-
   // Explicitly ask to fetch hashes for |extension|. If |force| is true,
   // we will always check the validity of the verified_contents.json and
   // re-check the contents of the files in the filesystem.
   void DoFetch(const Extension* extension, bool force);
 
-  // ExtensionRegistryObserver interface
-  virtual void OnExtensionLoaded(content::BrowserContext* browser_context,
-                                 const Extension* extension) OVERRIDE;
-  virtual void OnExtensionUnloaded(
-      content::BrowserContext* browser_context,
-      const Extension* extension,
-      UnloadedExtensionInfo::Reason reason) OVERRIDE;
+  // These should be called when an extension is loaded or unloaded.
+  virtual void ExtensionLoaded(const Extension* extension);
+  virtual void ExtensionUnloaded(const Extension* extension);
 
  private:
   // Callback for when a job getting content hashes has completed.
@@ -82,9 +72,6 @@
   typedef std::map<IdAndVersion, scoped_refptr<ContentHashFetcherJob> > JobMap;
   JobMap jobs_;
 
-  // For observing the ExtensionRegistry.
-  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
-
   // Used for binding callbacks passed to jobs.
   base::WeakPtrFactory<ContentHashFetcher> weak_ptr_factory_;
 
diff --git a/extensions/browser/content_verifier.cc b/extensions/browser/content_verifier.cc
index 1bfa1f4..52321c9 100644
--- a/extensions/browser/content_verifier.cc
+++ b/extensions/browser/content_verifier.cc
@@ -12,6 +12,7 @@
 #include "extensions/browser/content_hash_fetcher.h"
 #include "extensions/browser/content_hash_reader.h"
 #include "extensions/browser/content_verifier_delegate.h"
+#include "extensions/browser/content_verifier_io_data.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_l10n_util.h"
@@ -20,51 +21,60 @@
 
 ContentVerifier::ContentVerifier(content::BrowserContext* context,
                                  ContentVerifierDelegate* delegate)
-    : context_(context),
+    : shutdown_(false),
+      context_(context),
       delegate_(delegate),
       fetcher_(new ContentHashFetcher(
           context,
           delegate,
-          base::Bind(&ContentVerifier::OnFetchComplete, this))) {
+          base::Bind(&ContentVerifier::OnFetchComplete, this))),
+      observer_(this),
+      io_data_(new ContentVerifierIOData) {
 }
 
 ContentVerifier::~ContentVerifier() {
 }
 
 void ContentVerifier::Start() {
-  fetcher_->Start();
+  ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+  observer_.Add(registry);
 }
 
 void ContentVerifier::Shutdown() {
+  shutdown_ = true;
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO,
+      FROM_HERE,
+      base::Bind(&ContentVerifierIOData::Clear, io_data_));
+  observer_.RemoveAll();
   fetcher_.reset();
-  delegate_.reset();
 }
 
 ContentVerifyJob* ContentVerifier::CreateJobFor(
     const std::string& extension_id,
     const base::FilePath& extension_root,
     const base::FilePath& relative_path) {
-  if (!fetcher_ || !delegate_)
-    return NULL;
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
-  const Extension* extension =
-      registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+  const ContentVerifierIOData::ExtensionData* data =
+      io_data_->GetData(extension_id);
+  if (!data)
+    return NULL;
 
   std::set<base::FilePath> paths;
   paths.insert(relative_path);
-  if (!ShouldVerifyAnyPaths(extension, paths))
+  if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
     return NULL;
 
   // TODO(asargent) - we can probably get some good performance wins by having
   // a cache of ContentHashReader's that we hold onto past the end of each job.
   return new ContentVerifyJob(
       new ContentHashReader(extension_id,
-                            *extension->version(),
+                            data->version,
                             extension_root,
                             relative_path,
                             delegate_->PublicKey()),
-      base::Bind(&ContentVerifier::VerifyFailed, this, extension->id()));
+      base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
 }
 
 void ContentVerifier::VerifyFailed(const std::string& extension_id,
@@ -76,6 +86,8 @@
         base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
     return;
   }
+  if (shutdown_)
+    return;
 
   VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
 
@@ -83,11 +95,7 @@
   const Extension* extension =
       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
 
-  if (!delegate_ || !extension)
-    return;
-
-  ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
-  if (mode < ContentVerifierDelegate::ENFORCE)
+  if (!extension)
     return;
 
   if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
@@ -99,11 +107,57 @@
   }
 }
 
+void ContentVerifier::OnExtensionLoaded(
+    content::BrowserContext* browser_context,
+    const Extension* extension) {
+  if (shutdown_)
+    return;
+
+  ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
+  if (mode != ContentVerifierDelegate::NONE) {
+    scoped_ptr<ContentVerifierIOData::ExtensionData> data(
+        new ContentVerifierIOData::ExtensionData(
+            delegate_->GetBrowserImagePaths(extension),
+            extension->version() ? *extension->version() : base::Version()));
+    content::BrowserThread::PostTask(content::BrowserThread::IO,
+                                     FROM_HERE,
+                                     base::Bind(&ContentVerifierIOData::AddData,
+                                                io_data_,
+                                                extension->id(),
+                                                base::Passed(&data)));
+    fetcher_->ExtensionLoaded(extension);
+  }
+}
+
+void ContentVerifier::OnExtensionUnloaded(
+    content::BrowserContext* browser_context,
+    const Extension* extension,
+    UnloadedExtensionInfo::Reason reason) {
+  if (shutdown_)
+    return;
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO,
+      FROM_HERE,
+      base::Bind(
+          &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
+  if (fetcher_)
+    fetcher_->ExtensionUnloaded(extension);
+}
+
+void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
+                                            bool shouldVerifyAnyPathsResult) {
+  if (shouldVerifyAnyPathsResult)
+    delegate_->VerifyFailed(extension_id);
+}
+
 void ContentVerifier::OnFetchComplete(
     const std::string& extension_id,
     bool success,
     bool was_force_check,
     const std::set<base::FilePath>& hash_mismatch_paths) {
+  if (shutdown_)
+    return;
+
   VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
 
   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
@@ -113,34 +167,38 @@
     return;
 
   ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
-  if (mode < ContentVerifierDelegate::ENFORCE)
-    return;
-
-  if (!success && mode < ContentVerifierDelegate::ENFORCE_STRICT)
-    return;
-
-  if ((was_force_check && !success) ||
-      ShouldVerifyAnyPaths(extension, hash_mismatch_paths))
+  if (was_force_check && !success &&
+      mode == ContentVerifierDelegate::ENFORCE_STRICT) {
+    // We weren't able to get verified_contents.json or weren't able to compute
+    // hashes.
     delegate_->VerifyFailed(extension_id);
+  } else {
+    content::BrowserThread::PostTaskAndReplyWithResult(
+        content::BrowserThread::IO,
+        FROM_HERE,
+        base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
+                   this,
+                   extension_id,
+                   extension->path(),
+                   hash_mismatch_paths),
+        base::Bind(
+            &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
+  }
 }
 
 bool ContentVerifier::ShouldVerifyAnyPaths(
-    const Extension* extension,
+    const std::string& extension_id,
+    const base::FilePath& extension_root,
     const std::set<base::FilePath>& relative_paths) {
-  if (!delegate_ || !extension || !extension->version())
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  const ContentVerifierIOData::ExtensionData* data =
+      io_data_->GetData(extension_id);
+  if (!data)
     return false;
 
-  ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
-  if (mode < ContentVerifierDelegate::ENFORCE)
-    return false;
+  const std::set<base::FilePath>& browser_images = data->browser_image_paths;
 
-  // Images used in the browser get transcoded during install, so skip
-  // checking them for now.  TODO(asargent) - see if we can cache this list
-  // for a given extension id/version pair.
-  std::set<base::FilePath> browser_images =
-      delegate_->GetBrowserImagePaths(extension);
-
-  base::FilePath locales_dir = extension->path().Append(kLocaleFolder);
+  base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
   scoped_ptr<std::set<std::string> > all_locales;
 
   for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
@@ -154,7 +212,7 @@
     if (ContainsKey(browser_images, relative_path))
       continue;
 
-    base::FilePath full_path = extension->path().Append(relative_path);
+    base::FilePath full_path = extension_root.Append(relative_path);
     if (locales_dir.IsParent(full_path)) {
       if (!all_locales) {
         // TODO(asargent) - see if we can cache this list longer to avoid
diff --git a/extensions/browser/content_verifier.h b/extensions/browser/content_verifier.h
index 69b6f04..4520c04 100644
--- a/extensions/browser/content_verifier.h
+++ b/extensions/browser/content_verifier.h
@@ -11,7 +11,11 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/version.h"
+#include "extensions/browser/content_verifier_delegate.h"
 #include "extensions/browser/content_verify_job.h"
+#include "extensions/browser/extension_registry_observer.h"
 
 namespace base {
 class FilePath;
@@ -25,12 +29,13 @@
 
 class Extension;
 class ContentHashFetcher;
-class ContentVerifierDelegate;
+class ContentVerifierIOData;
 
 // Used for managing overall content verification - both fetching content
 // hashes as needed, and supplying job objects to verify file contents as they
 // are read.
-class ContentVerifier : public base::RefCountedThreadSafe<ContentVerifier> {
+class ContentVerifier : public base::RefCountedThreadSafe<ContentVerifier>,
+                        public ExtensionRegistryObserver {
  public:
   // Takes ownership of |delegate|.
   ContentVerifier(content::BrowserContext* context,
@@ -49,10 +54,13 @@
   void VerifyFailed(const std::string& extension_id,
                     ContentVerifyJob::FailureReason reason);
 
-  void OnFetchComplete(const std::string& extension_id,
-                       bool success,
-                       bool was_force_check,
-                       const std::set<base::FilePath>& hash_mismatch_paths);
+  // ExtensionRegistryObserver interface
+  virtual void OnExtensionLoaded(content::BrowserContext* browser_context,
+                                 const Extension* extension) OVERRIDE;
+  virtual void OnExtensionUnloaded(
+      content::BrowserContext* browser_context,
+      const Extension* extension,
+      UnloadedExtensionInfo::Reason reason) OVERRIDE;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ContentVerifier);
@@ -60,19 +68,37 @@
   friend class base::RefCountedThreadSafe<ContentVerifier>;
   virtual ~ContentVerifier();
 
+  void OnFetchComplete(const std::string& extension_id,
+                       bool success,
+                       bool was_force_check,
+                       const std::set<base::FilePath>& hash_mismatch_paths);
+
+  void OnFetchCompleteHelper(const std::string& extension_id,
+                             bool shouldVerifyAnyPathsResult);
+
   // Returns true if any of the paths in |relative_paths| *should* have their
   // contents verified. (Some files get transcoded during the install process,
   // so we don't want to verify their contents because they are expected not
   // to match).
-  bool ShouldVerifyAnyPaths(const Extension* extension,
+  bool ShouldVerifyAnyPaths(const std::string& extension_id,
+                            const base::FilePath& extension_root,
                             const std::set<base::FilePath>& relative_paths);
 
+  // Set to true once we've begun shutting down.
+  bool shutdown_;
+
   content::BrowserContext* context_;
 
   scoped_ptr<ContentVerifierDelegate> delegate_;
 
   // For fetching content hash signatures.
   scoped_ptr<ContentHashFetcher> fetcher_;
+
+  // For observing the ExtensionRegistry.
+  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
+
+  // Data that should only be used on the IO thread.
+  scoped_refptr<ContentVerifierIOData> io_data_;
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/content_verifier_delegate.h b/extensions/browser/content_verifier_delegate.h
index 5f5e57c..4d907cd 100644
--- a/extensions/browser/content_verifier_delegate.h
+++ b/extensions/browser/content_verifier_delegate.h
@@ -55,8 +55,8 @@
 
   virtual ~ContentVerifierDelegate() {}
 
-  // This should return true if the given extension should have its content
-  // verified.
+  // This should return what verification mode is appropriate for the given
+  // extension, if any.
   virtual Mode ShouldBeVerified(const Extension& extension) = 0;
 
   // Should return the public key to use for validating signatures via the two
diff --git a/extensions/browser/content_verifier_io_data.cc b/extensions/browser/content_verifier_io_data.cc
new file mode 100644
index 0000000..e797488
--- /dev/null
+++ b/extensions/browser/content_verifier_io_data.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 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 "extensions/browser/content_verifier_io_data.h"
+
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+ContentVerifierIOData::ExtensionData::ExtensionData(
+    const std::set<base::FilePath>& browser_image_paths,
+    const base::Version& version) {
+  this->browser_image_paths = browser_image_paths;
+  this->version = version;
+}
+
+ContentVerifierIOData::ContentVerifierIOData() {
+}
+
+ContentVerifierIOData::ExtensionData::~ExtensionData() {
+}
+
+ContentVerifierIOData::~ContentVerifierIOData() {
+}
+
+void ContentVerifierIOData::AddData(const std::string& extension_id,
+                                    scoped_ptr<ExtensionData> data) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  data_map_[extension_id] = linked_ptr<ExtensionData>(data.release());
+}
+
+void ContentVerifierIOData::RemoveData(const std::string& extension_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  std::map<std::string, linked_ptr<ExtensionData> >::iterator found =
+      data_map_.find(extension_id);
+  if (found != data_map_.end())
+    data_map_.erase(found);
+}
+
+void ContentVerifierIOData::Clear() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  data_map_.clear();
+}
+
+const ContentVerifierIOData::ExtensionData* ContentVerifierIOData::GetData(
+    const std::string& extension_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  std::map<std::string, linked_ptr<ExtensionData> >::iterator found =
+      data_map_.find(extension_id);
+  if (found != data_map_.end())
+    return found->second.get();
+  else
+    return NULL;
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/content_verifier_io_data.h b/extensions/browser/content_verifier_io_data.h
new file mode 100644
index 0000000..64596f5
--- /dev/null
+++ b/extensions/browser/content_verifier_io_data.h
@@ -0,0 +1,53 @@
+// Copyright 2014 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.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_VERIFIER_IO_DATA_H_
+#define EXTENSIONS_BROWSER_CONTENT_VERIFIER_IO_DATA_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/version.h"
+
+namespace extensions {
+
+// A helper class for keeping track of data for the ContentVerifier that should
+// only be accessed on the IO thread.
+class ContentVerifierIOData
+    : public base::RefCountedThreadSafe<ContentVerifierIOData> {
+ public:
+  struct ExtensionData {
+    std::set<base::FilePath> browser_image_paths;
+    base::Version version;
+
+    ExtensionData(const std::set<base::FilePath>& browser_image_paths,
+                  const base::Version& version);
+    ~ExtensionData();
+  };
+
+  ContentVerifierIOData();
+
+  void AddData(const std::string& extension_id, scoped_ptr<ExtensionData> data);
+  void RemoveData(const std::string& extension_id);
+  void Clear();
+
+  // This should be called on the IO thread, and the return value should not
+  // be retained or used on other threads.
+  const ExtensionData* GetData(const std::string& extension_id);
+
+ protected:
+  friend class base::RefCountedThreadSafe<ContentVerifierIOData>;
+  virtual ~ContentVerifierIOData();
+
+  std::map<std::string, linked_ptr<ExtensionData> > data_map_;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_CONTENT_VERIFIER_IO_DATA_H_
diff --git a/extensions/browser/content_verify_job.cc b/extensions/browser/content_verify_job.cc
index 1bf68cb..f8618bc 100644
--- a/extensions/browser/content_verify_job.cc
+++ b/extensions/browser/content_verify_job.cc
@@ -137,15 +137,16 @@
     return true;
   std::string final(crypto::kSHA256Length, 0);
   current_hash_->Finish(string_as_array(&final), final.size());
+  current_hash_.reset();
+  current_hash_byte_count_ = 0;
+
+  int block = current_block_++;
 
   const std::string* expected_hash = NULL;
-  if (!hash_reader_->GetHashForBlock(current_block_, &expected_hash) ||
+  if (!hash_reader_->GetHashForBlock(block, &expected_hash) ||
       *expected_hash != final)
     return false;
 
-  current_hash_.reset();
-  current_hash_byte_count_ = 0;
-  current_block_++;
   return true;
 }
 
diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp
index 640738e..9c1f3a0 100644
--- a/extensions/extensions.gyp
+++ b/extensions/extensions.gyp
@@ -354,6 +354,8 @@
         'browser/content_verifier.cc',
         'browser/content_verifier.h',
         'browser/content_verifier_delegate.h',
+        'browser/content_verifier_io_data.cc',
+        'browser/content_verifier_io_data.h',
         'browser/content_verify_job.cc',
         'browser/content_verify_job.h',
         'browser/error_map.cc',