class TestCertificates(TestCase): BASE_MESSAGE = { 'id': ('javascript', 'predefinedentities', 'cert_db'), 'description': Matches('Access to the X509 certificate ' 'database'), 'signing_help': Matches('avoid interacting with the ' 'certificate and trust databases'), 'signing_severity': 'high' } @parametrize('interface', ('nsIX509CertDB', 'nsIX509CertDB2', 'nsIX509CertList', 'nsICertOverrideService')) def test_cert_db_interfaces(self, interface): """Test that use of the certificate DB interfaces raises a signing warning.""" self.run_script('Cc[""].getService(Ci.{0});'.format(interface)) self.assert_warnings(self.BASE_MESSAGE) @parametrize('contract', ('@mozilla.org/security/x509certdb;1', '@mozilla.org/security/x509certlist;1', '@mozilla.org/security/certoverride;1')) def test_cert_db_contracts(self, contract): """Test that access to the certificate DB contract IDs raises a signing warning.""" self.run_script('Cc["{0}"]'.format(contract)) self.assert_warnings(self.BASE_MESSAGE)
class TestCTypes(TestCase): BASE_MESSAGE = { 'id': ('testcases_javascript', 'security', 'ctypes'), 'description': Matches('ctypes.*can lead to serious, and often ' 'exploitable, errors'), 'signing_help': Matches('avoid.*native binaries'), 'signing_severity': 'high' } def test_ctypes_usage(self): """Test that use of the ctypes global triggers a signing warning.""" self.run_script('ctypes.open("foo.so")') self.assert_warnings(self.BASE_MESSAGE) @parametrize('script', ( 'Cu.import("resource://gre/modules/ctypes.jsm?foo");', 'Components.utils.import("resource:///modules/ctypes.jsm");', )) def test_ctypes_module(self, script): """Test that references to ctypes.jsm trigger a signing warning.""" self.run_script(script) self.assert_warnings(self.BASE_MESSAGE)
def test_setTimeout_dynamic(self, setTimeout_pattern): """Test that running setTimeout or setInterval with a dynamic, non-function value results in the appropriate warning.""" self.run_script(setTimeout_pattern.format(code='foo')) self.assert_warnings( dict( self.BASE_MESSAGE, description=Matches('function expressions as their first arg'), signing_help=Matches('do not ever call.*string arguments')))
def test_innerhtml_dynamic(self): """Test that dynamic values assigned to innerHTML are flagged.""" self.set_html('foo') self.assert_warnings({ 'id': (Exists(), 'set_%s' % self.method, 'variable_assignment'), 'message': Matches('Markup should not.*dynamically'), 'description': Matches('not been adequately sanitized') })
def test_document_write(self): """Test that any use of `document.write` usses a warning that it's deprecated.""" self.set_html('foo') self.assert_warnings({ 'id': ('js', 'document.write', 'evil'), 'message': Matches('document\.write.*strongly discouraged'), 'description': Matches('should not be used') })
def test_function_export(self, method): """Test that the use of function export APIs raises signing warnings.""" self.run_script('Components.utils.{0}(thing)'.format(method)) self.assert_warnings({ 'description': Matches('expose privileged functionality'), 'signing_help': Matches('exposing APIs to unprivileged code'), 'signing_severity': 'low' })
class TestPrototype(TestCase): MESSAGE = { 'id': ('testcases_javascript', 'performance', 'prototype_mutation'), 'message': Contains('deprecated'), 'description': Matches(r'`Object\.create`'), } def test_set__proto__(self): """Test changes to the __proto__ property are flagged.""" self.run_script('obj.__proto__ = foo;') self.assert_warnings(self.MESSAGE) def test_create_with__proto__(self): """Test that creating a object literals with `__proto__` properties is not flagged.""" self.run_script('var obj = {__proto__: foo};') self.run_script('var obj = {"__proto__": foo};') self.assert_silent() def test_setPrototypeOf(self): """Test that changes to object prototypes via `Object.setPrototypeOf` are flagged.""" self.run_script('Object.setPrototypeOf(obj, foo)') self.assert_warnings(self.MESSAGE)
def test_security_prefs(self, branch): """Test that preference branches flagged as security issues.""" # Check that instances not at the start of the string aren't # flagged. self.run_script('foo("thing, stuff, bleh.{0}")'.format(branch)) self.assert_silent() self.run_script('foo("{0}thing")'.format(branch)) self.assert_warnings({ 'description': Matches('severe security implications'), 'signing_help': Matches('by exception only'), 'signing_severity': 'high' })
def test_innerHTML_script(self, fragment): """Test that innerHTML assignments containing <script> nodes are flagged.""" self.set_html("'{0}'".format(fragment)) self.assert_warnings({ 'id': (Exists(), 'set_%s' % self.method, 'script_assignment'), 'message': Contains('Scripts'), 'description': Matches('should not be used'), 'signing_help': Matches('avoid including JavaScript'), 'signing_severity': 'medium' })
def test_innerHTML_event(self): """Test that innerHTML assignments containing event listeners are flagged.""" self.set_html(""" '<a onclick="doStuff()">Hello.</a>' """) self.assert_warnings({ 'id': (Exists(), 'set_%s' % self.method, 'event_assignment'), 'message': Contains('Event handler'), 'description': Matches('addEventListener'), 'signing_help': Matches('avoid including JavaScript'), 'signing_severity': 'medium' })
def test_contentScript_dynamic(self, code): """Test that assigning a dynamic value to a content script results in a warning.""" self.run_script(code) self.assert_warnings({ 'id': (Exists(), 'contentScript', 'set_non_literal'), 'message': '`contentScript` properties should not be used', 'description': Matches('dynamic values is dangerous and ' 'error-prone'), 'signing_help': Matches('do not use'), 'signing_severity': 'high' })
def test_proxy_filter(self): """Test that uses of proxy filters are flagged.""" self.run_script(""" Cc[thing].getService(Ci.nsIProtocolProxyService).registerFilter( filter); """) self.assert_warnings({ 'id': Contains('nsIProtocolProxyService.registerFilter'), 'description': Matches('direct arbitrary network traffic'), 'signing_help': Matches('must undergo manual code review'), 'signing_severity': 'low' })
def test_synchronous_sql(self, code): """Test that the use of synchronous Storage APIs is raised as a performance issue.""" self.run_script(code) self.assert_warnings({ 'description': Matches(r'Sqlite\.jsm.*`executeAsync`') })
def test_shallow_wrappers(self): """Test that the use of shallow wrappers results in a signing warning.""" self.run_script('XPCNativeWrapper(foo, "bar");') self.assert_warnings({ 'id': (Exists(), 'xpcnativewrapper', 'shallow'), 'message': Contains('Shallow XPCOM wrappers'), 'description': Matches('Shallow XPCOM'), 'signing_help': Matches('second and subsequent arguments'), 'signing_severity': 'high' })
def test_nsISound_play(self): """Test that `nsISound.play` is flagged for performance issues.""" self.run_script('Cc[""].getService(Ci.nsISound).play(foo)') self.assert_warnings({ 'id': Contains('nsISound.play'), 'description': Matches('synchronous.*freezes.*HTML5 audio'), })
def test_nsIAccessibleRetrieval(self): """Test that the entire `nsIAccessibleRetrieval` interface is flagged.""" self.run_script('foo.QueryInterface(Ci.nsIAccessibleRetrieval)') self.assert_warnings({ 'id': Contains('nsIAccessibleRetrieval'), 'description': Matches('performance degradation'), })
def test_nsIDNSService_resolve(self): """Test that `nsIDNSService.resolve` is flagged for performance issues.""" self.run_script('Cc[""].getService(Ci.nsIDNSService).resolve(foo)') self.assert_warnings({ 'id': Contains('nsIDNSService.resolve'), 'description': Matches('synchronous.*freeze.*asyncResolve'), })
def test_processNextEvent(self): """Test that calls to `processNextEvent` are flagged as performance/stability issues.""" self.run_script('foo.processNextEvent()') self.assert_warnings({ 'id': Contains('**.processNextEvent'), 'description': Matches('Spinning the event loop.*' 'deadlocks.*re-?entrancy.*async'), })
def test_exposed_props(self): """Test that assignment to __exposedProps__ raises signing warnings.""" self.run_script('obj.__exposedProps__ = {};') self.assert_warnings({ 'id': Contains('**.__exposedProps__'), 'message': Contains('deprecated'), 'description': Matches('`cloneInto` or `exportFunction`'), 'signing_help': Matches('expose APIs'), 'signing_severity': 'high' }) # Test that just getting the property is not flagged. self.setup_err() self.run_script('var foo = obj.__exposedProps__;') self.assert_silent()
def test_nsIProcess(self, script): """Test that uses of nsIProcess are flagged.""" self.run_script(script) self.assert_warnings({ 'message': Contains('nsIProcess is potentially dangerous'), 'signing_severity': 'high', 'signing_help': Matches('alternatives to directly launching ' 'executables') })
def test_open_sync(self, get_xhr, arg): """Test that the `open` method is flagged when called with a falsy third argument.""" self.run_script('let xhr = {get_xhr};' 'xhr.open(method, url, {arg});' .format(arg=arg, get_xhr=get_xhr)) self.assert_warnings({ 'id': Contains('nsIXMLHttpRequest.open'), 'description': Matches('Synchronous.*serious UI performance ' 'problems'), })
def test_dynamic_sql(self, script): """Test that the execution of SQL statements using dynamic values raises a warning.""" warning = {'id': Contains('executeSimpleSQL_dynamic'), 'description': Matches('dynamic parameter binding')} # Unknown value. self.run_script(script.format(code='"" + foo')) self.assert_warnings(warning) # Static string. self.setup_err() self.run_script(script.format(code='"SELECT ?"')) self.assert_no_warnings(warning)
class TestEval(TestCase): """Tests that unsafe uses of eval and similar functions are flagged.""" BASE_MESSAGE = { 'id': ('javascript', 'dangerous_global', 'eval'), 'description': Matches('Evaluation of strings as code'), 'signing_help': Matches('avoid evaluating strings'), 'signing_severity': 'high' } @pytest.fixture(params=('eval({code})', 'Function({code})', 'Function("foo", {code})')) def eval_pattern(self, request): return request.param def check_eval_literal(self, pattern): """Test that evaluating a literal results in the appropriate warnings for the literal's contents.""" CODE = """'XPCNativeWrapper(foo, "bar")'""" scripts = ( # Call eval directly. pattern.format(code=CODE), # Save to a variable first. ('var fooThing = {code}; '.format(code=CODE) + pattern.format(code='fooThing')), ) for script in scripts: self.setup_err() self.run_script(script) self.assert_warnings( {'id': (Exists(), 'xpcnativewrapper', 'shallow')}) @parametrize('arg', ('"foo"', 'foo')) def test_eval(self, eval_pattern, arg): """Test that any use of eval results in a warning.""" self.run_script(eval_pattern.format(code=arg)) self.assert_warnings(self.BASE_MESSAGE) def test_eval_literal(self, eval_pattern): """Test that evaluating a literal results in the appropriate warnings for the literal's contents.""" self.check_eval_literal(eval_pattern) # The branching factor for these will make your head spin. And it doesn't # even tests things declared in branches. Complete coverage is fine, and # all, but this is perhaps going a bit overboard. And underboard. @pytest.fixture(params=('setTimeout({code}, 100)', 'setInterval({code}, 100)')) def setTimeout_pattern(self, request): """Yields several variants of `setTimeout` and `setInterval`, each with a `{code}` format string replacement for its first parameter.""" return request.param callables = [ pattern.format(callable=callable) for callable, pattern in itertools.product(( 'function() {}', 'function x() {}', 'function* () {}', 'function* x() {}', '() => {}', '() => 1', 'declared_function', 'declared_generator'), ('({callable})', '({callable}).bind()')) ] @pytest.fixture(params=callables) def function_ish(self, request): """Yields a number of callable expression types, along with their counterparts returned by `.bind()`.""" return request.param def test_setTimeout_function(self, setTimeout_pattern, function_ish): """Test that setTimeout and setInterval called with function arguments is silent.""" base_script = ''' function declared_function() {} function* declared_generator() {} ''' self.run_script(base_script + setTimeout_pattern.format(code=function_ish)) self.assert_silent() @parametrize('declare', ('var thing = {callable};', 'const thing = {callable};', 'thing = {callable};', 'let thing = {callable};')) def test_setTimeout_var_function(self, setTimeout_pattern, function_ish, declare): base_script = ''' function declared_function() {} function* declared_generator() {} ''' declaration = declare.format(callable=function_ish) self.run_script(base_script + declaration + setTimeout_pattern.format(code='thing')) self.assert_silent() def test_setTimeout_literal(self, setTimeout_pattern): """Test that evaluating a literal results in the appropriate warnings for the literal's contents.""" self.check_eval_literal(setTimeout_pattern) def test_setTimeout_dynamic(self, setTimeout_pattern): """Test that running setTimeout or setInterval with a dynamic, non-function value results in the appropriate warning.""" self.run_script(setTimeout_pattern.format(code='foo')) self.assert_warnings( dict( self.BASE_MESSAGE, description=Matches('function expressions as their first arg'), signing_help=Matches('do not ever call.*string arguments'))) @parametrize( 'pattern', ('foo({{contentScript: {code}}})', 'x.contentScript = {code};')) def test_contentScript_literal(self, pattern): """Test that evaluating a literal results in the appropriate warnings for the literal's contents.""" # FIXME: These messages are currently missing the appropriate # context. self.skip_sanity_check() self.check_eval_literal(pattern) @parametrize('code', ('foo({contentScript: foo})', 'x.contentScript = foo;')) def test_contentScript_dynamic(self, code): """Test that assigning a dynamic value to a content script results in a warning.""" self.run_script(code) self.assert_warnings({ 'id': (Exists(), 'contentScript', 'set_non_literal'), 'message': '`contentScript` properties should not be used', 'description': Matches('dynamic values is dangerous and ' 'error-prone'), 'signing_help': Matches('do not use'), 'signing_severity': 'high' })
def test_other_prefs(self, branch): """Test that less security-sensitive preferences are flagged.""" self.run_script('foo("{0}bar")'.format(branch)) self.assert_warnings({'message': Matches('unsafe preference branch')})
def test_unsafeWindow(self): """Test that access to `unsafeWindow` is flagged.""" self.run_script('var foo = unsafeWindow.bar;') self.assert_warnings( {'description': Matches('unsafeWindow is insecure')})