普通文本  |  165行  |  6.71 KB

# Copyright (c) 2012 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.

import json
import logging
import os

from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome

class security_BundledExtensions(test.test):
    """Verify security properties of bundled (on-disk) extensions."""
    version = 1

    def load_baseline(self):
        """
        Loads the set of expected permissions.

        @return Dictionary of expected permissions.
        """
        bfile = open(os.path.join(self.bindir, 'baseline'))
        with open(os.path.join(self.bindir, 'baseline')) as bfile:
            baseline = []
            for line in bfile:
                if not line.startswith('#'):
                    baseline.append(line)
            baseline = json.loads(''.join(baseline))
        self._ignored_extension_ids = baseline['ignored_extension_ids']
        self._bundled_crx_baseline = baseline['bundled_crx_baseline']
        self._component_extension_baseline = baseline[
            'component_extension_baseline']
        self._official_components = baseline['official_components']
        self._extensions_info = None


    def _get_stable_extensions_info(self, ext):
        """
        Poll condition that verifies that we're getting a stable list of
        extensions from chrome.autotestPrivate.getExtensionInfo.

        @return list of dicts, each representing an extension.
        """
        logging.info("Poll")
        prev = self._extensions_info
        ext.ExecuteJavaScript('''
            window.__extensions_info = null;
            chrome.autotestPrivate.getExtensionsInfo(function(s) {
                window.__extensions_info = s.extensions;
            });
        ''')
        self._extensions_info = utils.poll_for_condition(
                lambda: ext.EvaluateJavaScript('window.__extensions_info'))
        if not prev:
            return False
        return len(prev) == len(self._extensions_info)

    def _get_extensions_info(self):
        """
        Calls _get_stable_extensions_info to get a stable list of extensions.
        Filters out extensions that are on the to-be-ignored list.

        @return list of dicts, each representing an extension.
        """
        with chrome.Chrome(logged_in=True, autotest_ext=True) as cr:
            ext = cr.autotest_ext
            if not ext:
                return None

            utils.poll_for_condition(
                    lambda: self._get_stable_extensions_info(ext),
                    sleep_interval=0.5, timeout=30)
            logging.debug("getExtensionsInfo:\n%s", self._extensions_info)
            filtered_info = []
            self._ignored_extension_ids.append(ext.extension_id)
            for rec in self._extensions_info:
                if not rec['id'] in self._ignored_extension_ids:
                    filtered_info.append(rec)
            self._extensions_info = filtered_info
            return filtered_info


    def compare_extensions(self):
        """Compare installed extensions to the expected set.

        Find the set of expected IDs.
        Find the set of observed IDs.
        Do set comparison to find the unexpected, and the expected/missing.

        """
        test_fail = False
        combined_baseline = (self._bundled_crx_baseline +
                             self._component_extension_baseline)
        # Filter out any baseline entries that don't apply to this board.
        # If there is no 'boards' limiter on a given record, the record applies.
        # If there IS a 'boards' limiter, check that it applies.
        board = utils.get_current_board()
        combined_baseline = [x for x in combined_baseline
                             if ((not 'boards' in x) or
                                 ('boards' in x and board in x['boards']))]

        observed_extensions = self._get_extensions_info()
        observed_ids = set([x['id'] for x in observed_extensions])
        expected_ids = set([x['id'] for x in combined_baseline])

        missing_ids = expected_ids - observed_ids
        missing_names = ['%s (%s)' % (x['name'], x['id'])
                         for x in combined_baseline if x['id'] in missing_ids]

        unexpected_ids = observed_ids - expected_ids
        unexpected_names = ['%s (%s)' % (x['name'], x['id'])
                            for x in observed_extensions if
                            x['id'] in unexpected_ids]

        good_ids = expected_ids.intersection(observed_ids)

        if missing_names:
            logging.error('Missing: %s', '; '.join(missing_names))
            test_fail = True
        if unexpected_names:
            logging.error('Unexpected: %s', '; '.join(unexpected_names))
            test_fail = True

        # For those IDs in both the expected-and-observed, ie, "good":
        #   Compare sets of expected-vs-actual API permissions, report diffs.
        #   Do same for host permissions.
        for good_id in good_ids:
            baseline = [x for x in combined_baseline if x['id'] == good_id][0]
            actual = [x for x in observed_extensions if x['id'] == good_id][0]
            # Check the API permissions.
            baseline_apis = set(baseline['apiPermissions'])
            actual_apis = set(actual['apiPermissions'])
            missing_apis = baseline_apis - actual_apis
            unexpected_apis = actual_apis - baseline_apis
            if missing_apis or unexpected_apis:
                test_fail = True
                self._report_attribute_diffs(missing_apis, unexpected_apis,
                                             actual)
            # Check the host permissions.
            baseline_hosts = set(baseline['effectiveHostPermissions'])
            actual_hosts = set(actual['effectiveHostPermissions'])
            missing_hosts = baseline_hosts - actual_hosts
            unexpected_hosts = actual_hosts - baseline_hosts
            if missing_hosts or unexpected_hosts:
                test_fail = True
                self._report_attribute_diffs(missing_hosts, unexpected_hosts,
                                             actual)
        if test_fail:
            # TODO(jorgelo): make this fail again, see crbug.com/343271.
            raise error.TestWarn('Baseline mismatch, see error log.')


    def _report_attribute_diffs(self, missing, unexpected, rec):
        logging.error('Problem with %s (%s):', rec['name'], rec['id'])
        if missing:
            logging.error('It no longer uses: %s', '; '.join(missing))
        if unexpected:
            logging.error('It unexpectedly uses: %s', '; '.join(unexpected))


    def run_once(self, mode=None):
        self.load_baseline()
        self.compare_extensions()