platform_Cryptohome, cryptohome.py: kill c-homed on dbus silence

Move platform_Cryptohome over to using the CryptohomeProxy and
ensure that any unreplied DBus call results in cryptohomed being
ABRT'd with the hope that we'll get a crash dump on daisy flake.

As we discover blocking operations, we can move them off the main
thread.

TEST=passes; kills -ABRT if cryptohomed gets a SIGSTOP during the test (as a proof of hang detection)
BUG=chromium:303677

Change-Id: Ia40b0120f4f7a918c8288bf65b5c251fb0671223
Reviewed-on: https://chromium-review.googlesource.com/178881
Reviewed-by: Kees Cook <keescook@chromium.org>
Commit-Queue: Will Drewry <wad@chromium.org>
Tested-by: Will Drewry <wad@chromium.org>
diff --git a/client/cros/cryptohome.py b/client/cros/cryptohome.py
index 8948827..bd4c0c6 100644
--- a/client/cros/cryptohome.py
+++ b/client/cros/cryptohome.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import dbus, logging, os, random, re, shutil, string
+import dbus, logging, os, random, re, shutil, string, time
 
 import common, constants
 from autotest_lib.client.bin import utils
@@ -15,12 +15,10 @@
     """Generic error for ChromiumOS-specific exceptions."""
     pass
 
-
 def __run_cmd(cmd):
     return utils.system_output(cmd + ' 2>&1', retain_output=True,
                                ignore_status=True).strip()
 
-
 def get_user_hash(user):
     """Get the user hash for the given user."""
     return utils.system_output(['cryptohome', '--action=obfuscate_user',
@@ -268,6 +266,11 @@
     return '@'.join([name, domain]).lower()
 
 
+def crash_cryptohomed():
+  # Try to kill cryptohomed so we get something to work with.
+  utils.system('pkill -ABRT cryptohomed')
+  time.sleep(2)  # Give it 2 seconds to dump
+
 class CryptohomeProxy:
     def __init__(self):
         BUSNAME = 'org.chromium.Cryptohome'
@@ -277,6 +280,17 @@
         obj = bus.get_object(BUSNAME, PATH)
         self.iface = dbus.Interface(obj, INTERFACE)
 
+    # Wrap all proxied calls to catch cryptohomed failures.
+    def __call(self, method, *args):
+        try:
+            return method(*args)
+        except dbus.exceptions.DBusException, e:
+            if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
+                logging.error('Cryptohome is not responding. Sending ABRT')
+                crash_cryptohomed()
+                raise ChromiumOSError('cryptohomed aborted. Check crashes!')
+            raise e
+
     def mount(self, user, password, create=False):
         """Mounts a cryptohome.
 
@@ -284,7 +298,10 @@
         TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
         heuristic, then remove this method. See <crosbug.com/20778>.
         """
-        return self.iface.Mount(user, password, create, False, [])[1]
+        out = self.__call(self.iface.Mount, user, password, create, False, [])
+        if not out:
+            return out
+        return out[1]
 
     def unmount(self, user):
         """Unmounts a cryptohome.
@@ -293,7 +310,7 @@
         TODO(ellyjones): Once there's a per-user unmount method, use it. See
         <crosbug.com/20778>.
         """
-        return self.iface.Unmount()
+        return self.__call(self.iface.Unmount)
 
     def is_mounted(self, user):
         """Tests whether a user's cryptohome is mounted."""
@@ -307,7 +324,7 @@
 
     def migrate(self, user, oldkey, newkey):
         """Migrates the specified user's cryptohome from one key to another."""
-        return self.iface.MigrateKey(user, oldkey, newkey)
+        return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
 
     def remove(self, user):
-        return self.iface.Remove(user)
+        return self.__call(self.iface.Remove, user)
diff --git a/client/site_tests/platform_CryptohomeMount/platform_CryptohomeMount.py b/client/site_tests/platform_CryptohomeMount/platform_CryptohomeMount.py
index fbb2107..88c2ca0 100644
--- a/client/site_tests/platform_CryptohomeMount/platform_CryptohomeMount.py
+++ b/client/site_tests/platform_CryptohomeMount/platform_CryptohomeMount.py
@@ -1,72 +1,43 @@
 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
-
-import logging
-import os
-import re
-import shutil
-
 from autotest_lib.client.bin import test
-from autotest_lib.client.common_lib import error, utils
-from autotest_lib.client.cros import constants
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros import cryptohome
 
 class platform_CryptohomeMount(test.test):
     version = 1
 
-    def __run_cmd(self, cmd):
-        result = utils.system_output(cmd + ' 2>&1', retain_output=True,
-                                     ignore_status=True)
-        return result
-
-
     def run_once(self):
         test_user = 'this_is_a_local_test_account@chromium.org';
         test_password = 'this_is_a_test_password';
         # Get the hash for the test user account
-        cmd = ('/usr/sbin/cryptohome --action=obfuscate_user --user='
-               + test_user)
-        user_hash = self.__run_cmd(cmd).strip()
+        user_hash = cryptohome.get_user_hash(test_user)
+        proxy = cryptohome.CryptohomeProxy()
 
         # Remove the test user account
-        cmd = ('/usr/sbin/cryptohome --action=remove --force --user='
-               + test_user)
-        self.__run_cmd(cmd)
-        # Ensure that the user directory does not exist
-        if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
-          raise error.TestFail('Cryptohome could not remove the test user.')
+        proxy.remove(test_user)
 
         # Mount the test user account
-        cmd = ('/usr/sbin/cryptohome --async --action=mount --user=' + test_user
-               + ' --password=' + test_password + ' --create')
-        self.__run_cmd(cmd)
-        # Ensure that the user directory exists
-        if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
-          raise error.TestFail('Cryptohome could not create the test user.')
-        # Ensure that the user directory is mounted
-        cmd = ('/usr/sbin/cryptohome --action=is_mounted')
-        if (self.__run_cmd(cmd).strip() == 'false'):
-          raise error.TestFail('Cryptohome created the user but did not mount.')
+        if not proxy.mount(test_user, test_password, create=True):
+          raise error.TestFail('Failed to create and mount the test user')
 
         # Unmount the directory
-        cmd = ('/usr/sbin/cryptohome --action=unmount')
-        self.__run_cmd(cmd)
+        if not proxy.unmount(test_user):
+          raise error.TestFail('Failed to unmount test user')
+
         # Ensure that the user directory is not mounted
-        cmd = ('/usr/sbin/cryptohome --action=is_mounted')
-        if (self.__run_cmd(cmd).strip() != 'false'):
-          raise error.TestFail('Cryptohome did not unmount the user.')
+        if proxy.is_mounted(test_user):
+          raise error.TestFail('Cryptohome mounted after unmount!')
 
         # Make sure that an incorrect password fails
         incorrect_password = 'this_is_an_incorrect_password'
-        cmd = ('/usr/sbin/cryptohome --async --action=mount --user=' + test_user
-               + ' --password=' + incorrect_password)
-        self.__run_cmd(cmd)
-        # Ensure that the user directory is not mounted
-        cmd = ('/usr/sbin/cryptohome --action=is_mounted')
-        if (self.__run_cmd(cmd).strip() != 'false'):
+        if proxy.mount(test_user, incorrect_password):
           raise error.TestFail('Cryptohome mounted with a bad password.')
+        # Ensure that the user directory is not mounted
+        if proxy.is_mounted(test_user):
+          raise error.TestFail('Cryptohome mounted even though mount() failed')
 
         # Remove the test user account
-        cmd = ('/usr/sbin/cryptohome --action=remove --force --user='
-               + test_user)
-        self.__run_cmd(cmd)
+        if not proxy.remove(test_user):
+          raise error.TestFail('Cryptohome could not clean up vault')