Home | History | Annotate | Download | only in platform_SecureEraseFile
      1 # Copyright 2017 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import glob
      6 import logging
      7 import os
      8 import tempfile
      9 
     10 from autotest_lib.client.bin import test
     11 from autotest_lib.client.common_lib import error
     12 from autotest_lib.client.common_lib import utils
     13 
     14 PARTITION_TEST_PATH = 'platform_SecureEraseFile_test_file'
     15 TEST_PATH = '/mnt/stateful_partition/' + PARTITION_TEST_PATH
     16 BINARY = '/usr/bin/secure_erase_file'
     17 DEVNAME_PREFIX = 'DEVNAME='
     18 
     19 class platform_SecureEraseFile(test.test):
     20     """Validate secure_erase_file tool behavior.
     21 
     22     We can't verify from this test that data has been destroyed from the
     23     underlying physical device, but we can confirm that it's not reachable from
     24     userspace.
     25     """
     26     version = 1
     27 
     28     def __write_test_file(self, path, blocksize, count):
     29         cmd = '/bin/dd if=/dev/urandom of=%s bs=%s count=%d' % (
     30                 path, blocksize, count)
     31         utils.run(cmd)
     32         if not os.path.exists(path):
     33             raise error.TestError('Failed to generate test file')
     34 
     35 
     36     def __get_partition(self, path):
     37         info = os.lstat(path)
     38         major = os.major(info.st_dev)
     39         minor = os.minor(info.st_dev)
     40         uevent_path = '/sys/dev/block/%d:%d/uevent' % (major, minor)
     41         with open(uevent_path, 'r') as uevent_file:
     42             for l in uevent_file.readlines():
     43                 if l.startswith(DEVNAME_PREFIX):
     44                     return '/dev/' + l[len(DEVNAME_PREFIX):].strip()
     45         raise error.TestError('Unable to find partition for path: ' + path)
     46 
     47 
     48     def __get_extents(self, path, partition):
     49         extents = []
     50         cmd = 'debugfs -R "extents %s" %s' % (path, partition)
     51         result = utils.run(cmd)
     52         for line in result.stdout.splitlines():
     53             # Discard header line.
     54             if line.startswith('Level'):
     55                 continue
     56             fields = line.split()
     57 
     58             # Ignore non-leaf extents
     59             if fields[0].strip('/') != fields[1]:
     60                 continue
     61             extents.append({'offset': fields[7], 'length': fields[10]})
     62 
     63         return extents
     64 
     65 
     66     def __verify_cleared(self, partition, extents):
     67         out_path = tempfile.mktemp()
     68         for e in extents:
     69             cmd = 'dd if=%s bs=4K skip=%s count=%s of=%s' % (
     70                    partition, e['offset'], e['length'], out_path)
     71             utils.run(cmd)
     72             with open(out_path, 'r') as out_file:
     73                 d = out_file.read()
     74                 for i, byte in enumerate(d):
     75                     if ord(byte) != 0x00 and ord(byte) != 0xFF:
     76                         logging.info('extent[%d] = %s', i, hex(ord(byte)))
     77                         raise error.TestError('Bad byte found')
     78 
     79 
     80     def __test_and_verify_cleared(self, blocksize, count):
     81         self.__write_test_file(TEST_PATH, blocksize, count)
     82         utils.run('sync')
     83 
     84         logging.info('original file contents: ')
     85         res = utils.run('xxd %s' % TEST_PATH)
     86         logging.info(res.stdout)
     87 
     88         partition = self.__get_partition(TEST_PATH)
     89         extents = self.__get_extents(PARTITION_TEST_PATH, partition)
     90         if len(extents) == 0:
     91             raise error.TestError('No extents found for ' + TEST_PATH)
     92 
     93         utils.run('%s %s' % (BINARY, TEST_PATH))
     94 
     95         # secure_erase_file confirms that the file has been erased and that its
     96         # contents are not accessible. If that is not the case, it will return
     97         # with a non-zero exit code.
     98         if os.path.exists(TEST_PATH):
     99             raise error.TestError('Secure Erase failed to unlink file.')
    100 
    101         self.__verify_cleared(partition, extents)
    102 
    103 
    104     def run_once(self):
    105         # Secure erase is only supported on eMMC today; pass if
    106         # no device is present.
    107         if len(glob.glob('/dev/mmcblk*')) == 0:
    108             raise error.TestNAError('Skipping test; no eMMC device found.')
    109 
    110         self.__test_and_verify_cleared('64K', 2)
    111         self.__test_and_verify_cleared('1M', 16)
    112 
    113     def after_run_once(self):
    114         if os.path.exists(TEST_PATH):
    115             os.unlink(TEST_PATH)
    116 
    117