# 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()