| # 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 glob |
| import logging |
| import os |
| import tempfile |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import utils |
| |
| PARTITION_TEST_PATH = 'platform_SecureEraseFile_test_file' |
| TEST_PATH = '/mnt/stateful_partition/' + PARTITION_TEST_PATH |
| BINARY = '/usr/bin/secure_erase_file' |
| DEVNAME_PREFIX = 'DEVNAME=' |
| |
| class platform_SecureEraseFile(test.test): |
| """Validate secure_erase_file tool behavior. |
| |
| We can't verify from this test that data has been destroyed from the |
| underlying physical device, but we can confirm that it's not reachable from |
| userspace. |
| """ |
| version = 1 |
| |
| def __write_test_file(self, path, blocksize, count): |
| cmd = '/bin/dd if=/dev/urandom of=%s bs=%s count=%d' % ( |
| path, blocksize, count) |
| utils.run(cmd) |
| if not os.path.exists(path): |
| raise error.TestError('Failed to generate test file') |
| |
| |
| def __get_partition(self, path): |
| info = os.lstat(path) |
| major = os.major(info.st_dev) |
| minor = os.minor(info.st_dev) |
| uevent_path = '/sys/dev/block/%d:%d/uevent' % (major, minor) |
| with open(uevent_path, 'r') as uevent_file: |
| for l in uevent_file.readlines(): |
| if l.startswith(DEVNAME_PREFIX): |
| return '/dev/' + l[len(DEVNAME_PREFIX):].strip() |
| raise error.TestError('Unable to find partition for path: ' + path) |
| |
| |
| def __get_extents(self, path, partition): |
| extents = [] |
| cmd = 'debugfs -R "extents %s" %s' % (path, partition) |
| result = utils.run(cmd) |
| for line in result.stdout.splitlines(): |
| # Discard header line. |
| if line.startswith('Level'): |
| continue |
| fields = line.split() |
| |
| # Ignore non-leaf extents |
| if fields[0].strip('/') != fields[1]: |
| continue |
| extents.append({'offset': fields[7], 'length': fields[10]}) |
| |
| return extents |
| |
| |
| def __verify_cleared(self, partition, extents): |
| out_path = tempfile.mktemp() |
| for e in extents: |
| cmd = 'dd if=%s bs=4K skip=%s count=%s of=%s' % ( |
| partition, e['offset'], e['length'], out_path) |
| utils.run(cmd) |
| with open(out_path, 'r') as out_file: |
| d = out_file.read() |
| for i, byte in enumerate(d): |
| if ord(byte) != 0x00 and ord(byte) != 0xFF: |
| logging.info('extent[%d] = %s', i, hex(ord(byte))) |
| raise error.TestError('Bad byte found') |
| |
| |
| def __test_and_verify_cleared(self, blocksize, count): |
| self.__write_test_file(TEST_PATH, blocksize, count) |
| utils.run('sync') |
| |
| logging.info('original file contents: ') |
| res = utils.run('xxd %s' % TEST_PATH) |
| logging.info(res.stdout) |
| |
| partition = self.__get_partition(TEST_PATH) |
| extents = self.__get_extents(PARTITION_TEST_PATH, partition) |
| if len(extents) == 0: |
| raise error.TestError('No extents found for ' + TEST_PATH) |
| |
| utils.run('%s %s' % (BINARY, TEST_PATH)) |
| |
| # secure_erase_file confirms that the file has been erased and that its |
| # contents are not accessible. If that is not the case, it will return |
| # with a non-zero exit code. |
| if os.path.exists(TEST_PATH): |
| raise error.TestError('Secure Erase failed to unlink file.') |
| |
| self.__verify_cleared(partition, extents) |
| |
| |
| def run_once(self): |
| # Secure erase is only supported on eMMC today; pass if |
| # no device is present. |
| if len(glob.glob('/dev/mmcblk*')) == 0: |
| raise error.TestNAError('Skipping test; no eMMC device found.') |
| |
| self.__test_and_verify_cleared('64K', 2) |
| self.__test_and_verify_cleared('1M', 16) |
| |
| def after_run_once(self): |
| if os.path.exists(TEST_PATH): |
| os.unlink(TEST_PATH) |
| |