| # Copyright 2017 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 |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server import autotest, test |
| from autotest_lib.client.common_lib.cros import tpm_utils |
| |
| |
| class firmware_Cr50InvalidateRW(test.test): |
| """ |
| Verify the inactive Cr50 header on the first login after cryptohome |
| restarts. |
| |
| There are two special cases this test covers: logging in after the TPM |
| owner is cleared and logging in as guest. |
| |
| After the tpm owner is cleared, corrupting the header will be done on |
| the second login. During guest login the owner wont be cleared. |
| """ |
| version = 1 |
| |
| GET_CRYPTOHOME_MESSAGE ='grep cryptohomed /var/log/messages' |
| SUCCESS = 'Successfully invalidated inactive Cr50 RW' |
| FAIL = 'Invalidating inactive Cr50 RW failed' |
| NO_ATTEMPT = 'Did not try to invalidate header' |
| LOGIN_ATTEMPTS = 5 |
| |
| def initialize(self, host): |
| """Initialize servo and cr50""" |
| super(firmware_Cr50InvalidateRW, self).initialize() |
| |
| self.host = host |
| self.client_at = autotest.Autotest(self.host) |
| |
| self.last_message = None |
| # get the messages already in /var/log/messages so we don't use them |
| # later. |
| self.check_for_invalidated_rw() |
| |
| |
| def check_for_invalidated_rw(self): |
| """Use /var/log/messages to see if the rw header was invalidated. |
| |
| Returns a string NO_ATTEMPT if cryptohome did not try to invalidate the |
| header or the cryptohome message if the attempt failed or succeeded. |
| """ |
| # Get the relevant messages from /var/log/messages |
| message_str = self.host.run(self.GET_CRYPTOHOME_MESSAGE, |
| verbose=False).stdout.strip() |
| |
| # Remove the messages we have seen in the past |
| if self.last_message: |
| message_str = message_str.rsplit(self.last_message, 1)[-1] |
| messages = message_str.split('\n') |
| |
| # Save the last message so we can identify new messages later |
| self.last_message = messages[-1] |
| |
| rv = self.NO_ATTEMPT |
| # print all cryptohome messages. |
| for message in messages: |
| logging.debug(message) |
| # Return the message that is related to the RW invalidate attempt |
| if self.FAIL in message or self.SUCCESS in message: |
| rv = message |
| return rv |
| |
| |
| def login(self, use_guest): |
| """Run the test to login.""" |
| if use_guest: |
| self.client_at.run_test('login_CryptohomeIncognito') |
| else: |
| self.client_at.run_test('login_LoginSuccess') |
| |
| |
| def login_and_verify(self, use_guest=False, corrupt_login=None): |
| """Verify the header is only invalidated on the specified login. |
| |
| login LOGIN_ATTEMPTS times. Verify that cryptohome only tries to corrupt |
| the inactive cr50 header on the specified login. If it tries on a |
| different login or fails to corrupt the header, raise an error. |
| |
| Args: |
| use_guest: True to login as guest |
| corrupt_login: The login attempt that we expect the header to be |
| corrupted on |
| |
| Raises: |
| TestError if the system attempts to corrupt the header on any login |
| that isn't corrupt_login or if an attepmt to corrupt the header |
| fails. |
| """ |
| for i in xrange(self.LOGIN_ATTEMPTS): |
| attempt = i + 1 |
| |
| self.login(use_guest) |
| result = self.check_for_invalidated_rw() |
| |
| message = '%slogin %d: %s' % ('guest ' if use_guest else '', |
| attempt, result) |
| logging.info(message) |
| |
| # Anytime the invalidate attempt fails raise an error |
| if self.FAIL in result: |
| raise error.TestError(message) |
| |
| # The header should be invalidated only on corrupt_login. Raise |
| # an error if it was invalidated on some other login or if |
| # cryptohome did not try on the first one. |
| if (attempt == corrupt_login) != (self.SUCCESS in result): |
| raise error.TestError('Unexpected result %s' % message) |
| |
| |
| def restart_cryptohome(self): |
| """Restart cryptohome |
| |
| Cryptohome only sends the command to corrupt the header once. Once it |
| has been sent it wont be sent again until cryptohome is restarted. |
| """ |
| self.host.run('restart cryptohomed') |
| |
| |
| def clear_tpm_owner(self): |
| """Clear the tpm owner.""" |
| logging.info('Clearing the TPM owner') |
| tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True) |
| |
| |
| def after_run_once(self): |
| """Print the run information after each successful run""" |
| logging.info('finished run %d', self.iteration) |
| |
| |
| def run_once(self, host): |
| """Login to validate ChromeOS corrupts the inactive header""" |
| # After clearing the tpm owner the header will be corrupted on the |
| # second login |
| self.clear_tpm_owner() |
| self.login_and_verify(corrupt_login=2) |
| |
| # The header is corrupted on the first login after cryptohome is reset |
| self.restart_cryptohome() |
| self.login_and_verify(corrupt_login=1) |
| |
| # Cryptohome is reset after reboot |
| self.host.reboot() |
| self.login_and_verify(corrupt_login=1) |
| |
| # The header is not corrupted after guest login, but will be corrupted |
| # on the first login after that. |
| self.restart_cryptohome() |
| self.login_and_verify(use_guest=True) |
| self.login_and_verify(corrupt_login=1) |