• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2012 The Chromium 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
5import json
6import logging
7import os
8
9from autotest_lib.client.bin import test, utils
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib.cros import chrome
12
13class security_BundledExtensions(test.test):
14    """Verify security properties of bundled (on-disk) extensions."""
15    version = 1
16
17    def load_baseline(self):
18        """
19        Loads the set of expected permissions.
20
21        @return Dictionary of expected permissions.
22        """
23        bfile = open(os.path.join(self.bindir, 'baseline'))
24        with open(os.path.join(self.bindir, 'baseline')) as bfile:
25            baseline = []
26            for line in bfile:
27                if not line.startswith('#'):
28                    baseline.append(line)
29            baseline = json.loads(''.join(baseline))
30        self._ignored_extension_ids = baseline['ignored_extension_ids']
31        self._bundled_crx_baseline = baseline['bundled_crx_baseline']
32        self._component_extension_baseline = baseline[
33            'component_extension_baseline']
34        self._official_components = baseline['official_components']
35        self._extensions_info = None
36
37
38    def _get_stable_extensions_info(self, ext):
39        """
40        Poll condition that verifies that we're getting a stable list of
41        extensions from chrome.autotestPrivate.getExtensionInfo.
42
43        @return list of dicts, each representing an extension.
44        """
45        logging.info("Poll")
46        prev = self._extensions_info
47        ext.ExecuteJavaScript('''
48            window.__extensions_info = null;
49            chrome.autotestPrivate.getExtensionsInfo(function(s) {
50                window.__extensions_info = s.extensions;
51            });
52        ''')
53        self._extensions_info = utils.poll_for_condition(
54                lambda: ext.EvaluateJavaScript('window.__extensions_info'))
55        if not prev:
56            return False
57        return len(prev) == len(self._extensions_info)
58
59    def _get_extensions_info(self):
60        """
61        Calls _get_stable_extensions_info to get a stable list of extensions.
62        Filters out extensions that are on the to-be-ignored list.
63
64        @return list of dicts, each representing an extension.
65        """
66        with chrome.Chrome(logged_in=True, autotest_ext=True) as cr:
67            ext = cr.autotest_ext
68            if not ext:
69                return None
70
71            utils.poll_for_condition(
72                    lambda: self._get_stable_extensions_info(ext),
73                    sleep_interval=0.5, timeout=30)
74            logging.debug("getExtensionsInfo:\n%s", self._extensions_info)
75            filtered_info = []
76            self._ignored_extension_ids.append(ext.extension_id)
77            for rec in self._extensions_info:
78                if not rec['id'] in self._ignored_extension_ids:
79                    filtered_info.append(rec)
80            self._extensions_info = filtered_info
81            return filtered_info
82
83
84    def compare_extensions(self):
85        """Compare installed extensions to the expected set.
86
87        Find the set of expected IDs.
88        Find the set of observed IDs.
89        Do set comparison to find the unexpected, and the expected/missing.
90
91        """
92        test_fail = False
93        combined_baseline = (self._bundled_crx_baseline +
94                             self._component_extension_baseline)
95        # Filter out any baseline entries that don't apply to this board.
96        # If there is no 'boards' limiter on a given record, the record applies.
97        # If there IS a 'boards' limiter, check that it applies.
98        board = utils.get_current_board()
99        combined_baseline = [x for x in combined_baseline
100                             if ((not 'boards' in x) or
101                                 ('boards' in x and board in x['boards']))]
102
103        observed_extensions = self._get_extensions_info()
104        observed_ids = set([x['id'] for x in observed_extensions])
105        expected_ids = set([x['id'] for x in combined_baseline])
106
107        missing_ids = expected_ids - observed_ids
108        missing_names = ['%s (%s)' % (x['name'], x['id'])
109                         for x in combined_baseline if x['id'] in missing_ids]
110
111        unexpected_ids = observed_ids - expected_ids
112        unexpected_names = ['%s (%s)' % (x['name'], x['id'])
113                            for x in observed_extensions if
114                            x['id'] in unexpected_ids]
115
116        good_ids = expected_ids.intersection(observed_ids)
117
118        if missing_names:
119            logging.error('Missing: %s', '; '.join(missing_names))
120            test_fail = True
121        if unexpected_names:
122            logging.error('Unexpected: %s', '; '.join(unexpected_names))
123            test_fail = True
124
125        # For those IDs in both the expected-and-observed, ie, "good":
126        #   Compare sets of expected-vs-actual API permissions, report diffs.
127        #   Do same for host permissions.
128        for good_id in good_ids:
129            baseline = [x for x in combined_baseline if x['id'] == good_id][0]
130            actual = [x for x in observed_extensions if x['id'] == good_id][0]
131            # Check the API permissions.
132            baseline_apis = set(baseline['apiPermissions'])
133            actual_apis = set(actual['apiPermissions'])
134            missing_apis = baseline_apis - actual_apis
135            unexpected_apis = actual_apis - baseline_apis
136            if missing_apis or unexpected_apis:
137                test_fail = True
138                self._report_attribute_diffs(missing_apis, unexpected_apis,
139                                             actual)
140            # Check the host permissions.
141            baseline_hosts = set(baseline['effectiveHostPermissions'])
142            actual_hosts = set(actual['effectiveHostPermissions'])
143            missing_hosts = baseline_hosts - actual_hosts
144            unexpected_hosts = actual_hosts - baseline_hosts
145            if missing_hosts or unexpected_hosts:
146                test_fail = True
147                self._report_attribute_diffs(missing_hosts, unexpected_hosts,
148                                             actual)
149        if test_fail:
150            # TODO(jorgelo): make this fail again, see crbug.com/343271.
151            raise error.TestWarn('Baseline mismatch, see error log.')
152
153
154    def _report_attribute_diffs(self, missing, unexpected, rec):
155        logging.error('Problem with %s (%s):', rec['name'], rec['id'])
156        if missing:
157            logging.error('It no longer uses: %s', '; '.join(missing))
158        if unexpected:
159            logging.error('It unexpectedly uses: %s', '; '.join(unexpected))
160
161
162    def run_once(self, mode=None):
163        self.load_baseline()
164        self.compare_extensions()
165