Added try_except_continue plugin
Along with a 'try, except, pass' check, we should also check for the similar existance of 'try, except, continue', which raises the same type of security implications, given the similar type of functionality. Using 'continue' in place of 'pass' (inside a loop) currently allows code to bypass the 'try, except, pass' warning. Change-Id: I3e7ce037518875c5f5e46e26e1d72ef878f78a2f
This commit is contained in:
parent
bee1d23f3d
commit
cac2f22dee
|
@ -144,6 +144,7 @@ Usage::
|
||||||
B109 password_config_option_not_marked_secret
|
B109 password_config_option_not_marked_secret
|
||||||
B110 try_except_pass
|
B110 try_except_pass
|
||||||
B111 execute_with_run_as_root_equals_true
|
B111 execute_with_run_as_root_equals_true
|
||||||
|
B112 try_except_continue
|
||||||
B201 flask_debug_true
|
B201 flask_debug_true
|
||||||
B301 pickle
|
B301 pickle
|
||||||
B302 marshal
|
B302 marshal
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Copyright 2016 IBM Corp.
|
||||||
|
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
r"""
|
||||||
|
=============================================
|
||||||
|
B112: Test for a continue in the except block
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Errors in Python code bases are typically communicated using ``Exceptions``.
|
||||||
|
An exception object is 'raised' in the event of an error and can be 'caught' at
|
||||||
|
a later point in the program, typically some error handling or logging action
|
||||||
|
will then be performed.
|
||||||
|
|
||||||
|
However, it is possible to catch an exception and silently ignore it while in
|
||||||
|
a loop. This is illustrated with the following example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
while keep_going:
|
||||||
|
try:
|
||||||
|
do_some_stuff()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
This pattern is considered bad practice in general, but also represents a
|
||||||
|
potential security issue. A larger than normal volume of errors from a service
|
||||||
|
can indicate an attempt is being made to disrupt or interfere with it. Thus
|
||||||
|
errors should, at the very least, be logged.
|
||||||
|
|
||||||
|
There are rare situations where it is desirable to suppress errors, but this is
|
||||||
|
typically done with specific exception types, rather than the base Exception
|
||||||
|
class (or no type). To accommodate this, the test may be configured to ignore
|
||||||
|
'try, except, continue' where the exception is typed. For example, the
|
||||||
|
following would not generate a warning if the configuration option
|
||||||
|
``checked_typed_exception`` is set to False:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
while keep_going:
|
||||||
|
try:
|
||||||
|
do_some_stuff()
|
||||||
|
except ZeroDivisionError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
**Config Options:**
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
try_except_continue:
|
||||||
|
check_typed_exception: True
|
||||||
|
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
>> Issue: Try, Except, Continue detected.
|
||||||
|
Severity: Low Confidence: High
|
||||||
|
Location: ./examples/try_except_continue.py:5
|
||||||
|
4 a = i
|
||||||
|
5 except:
|
||||||
|
6 continue
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
- https://security.openstack.org
|
||||||
|
|
||||||
|
.. versionadded:: 1.0.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
|
||||||
|
import bandit
|
||||||
|
from bandit.core import test_properties as test
|
||||||
|
|
||||||
|
|
||||||
|
def gen_config(name):
|
||||||
|
if name == 'try_except_continue':
|
||||||
|
return {'check_typed_exception': True}
|
||||||
|
|
||||||
|
|
||||||
|
@test.takes_config
|
||||||
|
@test.checks('ExceptHandler')
|
||||||
|
@test.test_id('B112')
|
||||||
|
def try_except_continue(context, config):
|
||||||
|
node = context.node
|
||||||
|
if len(node.body) == 1:
|
||||||
|
if (not config['check_typed_exception'] and
|
||||||
|
node.type is not None and
|
||||||
|
node.type.id != 'Exception'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(node.body[0], ast.Continue):
|
||||||
|
return bandit.Issue(
|
||||||
|
severity=bandit.LOW,
|
||||||
|
confidence=bandit.HIGH,
|
||||||
|
text=("Try, Except, Continue detected."))
|
|
@ -0,0 +1,5 @@
|
||||||
|
-------------------------
|
||||||
|
B112: try_except_continue
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: bandit.plugins.try_except_continue
|
|
@ -0,0 +1,32 @@
|
||||||
|
# bad
|
||||||
|
for i in {0,1}:
|
||||||
|
try:
|
||||||
|
a = i
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# bad
|
||||||
|
while keep_trying:
|
||||||
|
try:
|
||||||
|
a = 1
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# bad
|
||||||
|
for i in {0,2}:
|
||||||
|
try:
|
||||||
|
a = i
|
||||||
|
except ZeroDivisionError:
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
a = 2
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
while keep_trying:
|
||||||
|
try:
|
||||||
|
a = 1
|
||||||
|
except:
|
||||||
|
a = 2
|
|
@ -97,6 +97,9 @@ bandit.plugins =
|
||||||
# bandit/plugins/secret_config_options.py
|
# bandit/plugins/secret_config_options.py
|
||||||
password_config_option_not_marked_secret = bandit.plugins.secret_config_option:password_config_option_not_marked_secret
|
password_config_option_not_marked_secret = bandit.plugins.secret_config_option:password_config_option_not_marked_secret
|
||||||
|
|
||||||
|
# bandit/plugins/try_except_continue.py
|
||||||
|
try_except_continue = bandit.plugins.try_except_continue:try_except_continue
|
||||||
|
|
||||||
# bandit/plugins/try_except_pass.py
|
# bandit/plugins/try_except_pass.py
|
||||||
try_except_pass = bandit.plugins.try_except_pass:try_except_pass
|
try_except_pass = bandit.plugins.try_except_pass:try_except_pass
|
||||||
|
|
||||||
|
|
|
@ -406,6 +406,21 @@ class FunctionalTests(testtools.TestCase):
|
||||||
|
|
||||||
self.check_example('partial_path_process.py', expect)
|
self.check_example('partial_path_process.py', expect)
|
||||||
|
|
||||||
|
def test_try_except_continue(self):
|
||||||
|
'''Test try, except, continue detection.'''
|
||||||
|
expect = {'SEVERITY': {'LOW': 3},
|
||||||
|
'CONFIDENCE': {'HIGH': 3}}
|
||||||
|
|
||||||
|
self.check_example('try_except_continue.py', expect)
|
||||||
|
|
||||||
|
test = next((x for x in self.b_mgr.b_ts.tests['ExceptHandler']
|
||||||
|
if x.__name__ == 'try_except_continue'))
|
||||||
|
test._config = {'check_typed_exception': False}
|
||||||
|
expect = {'SEVERITY': {'LOW': 2},
|
||||||
|
'CONFIDENCE': {'HIGH': 2}}
|
||||||
|
|
||||||
|
self.check_example('try_except_continue.py', expect)
|
||||||
|
|
||||||
def test_try_except_pass(self):
|
def test_try_except_pass(self):
|
||||||
'''Test try, except pass detection.'''
|
'''Test try, except pass detection.'''
|
||||||
expect = {'SEVERITY': {'LOW': 3},
|
expect = {'SEVERITY': {'LOW': 3},
|
||||||
|
|
Loading…
Reference in New Issue