Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Usage::
B323 unverified_context
B324 hashlib_new_insecure_functions
B325 tempnam
B326 ast_overflow
B401 import_telnetlib
B402 import_ftplib
B403 import_pickle
Expand Down
38 changes: 38 additions & 0 deletions bandit/blacklists/calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,29 @@
| | | - os.tmpnam | |
+------+---------------------+------------------------------------+-----------+

B326: ast_overflow
-------------------

It is possible to crash the Python interpreter by passing sufficiently
large/complex strings to ast.literal_eval(), ast.parse(), compile(),
dbm.dumb.open(), eval() or exec() due to stack depth limitations in Python’s
AST compiler. Ensure these functions are not used on untrusted data.

For further information:
https://docs.python.org/3.7/library/ast.html#ast.parse
https://bugs.python.org/issue32758

+------+---------------------+------------------------------------+-----------+
| ID | Name | Calls | Severity |
+======+=====================+====================================+===========+
| B326 | ast_overflow | - ast.literal_eval | Low |
| | | - ast.parse | |
| | | - compile | |
| | | - dbm.dumb.open | |
| | | - eval | |
| | | - exec | |
+------+---------------------+------------------------------------+-----------+

"""

from bandit.blacklists import utils
Expand Down Expand Up @@ -569,4 +592,19 @@ def gen_blacklist():
'attacks. Consider using tmpfile() instead.'
))

sets.append(utils.build_conf_dict(
'ast_overflow', 'B326',
['ast.literal_eval',
'ast.parse',
'compile',
'dbm.dumb.open',
'eval'],
'It is possible to crash the Python interpreter by passing '
'sufficiently large/complex strings to ast.literal_eval(), '
'ast.parse(), compile(), dbm.dumb.open(), eval() or exec() due to '
'stack depth limitations in Python’s AST compiler. Ensure these '
'functions are not used on untrusted data.',
'LOW'
))

return {'Call': sets}
43 changes: 43 additions & 0 deletions bandit/plugins/ast_overflow_exec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- coding:utf-8 -*-
#
# 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.

import six

import bandit
from bandit.core import test_properties as test


def ast_overflow_exec_issue():
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
text=('It is possible to crash the Python interpreter by passing '
'sufficiently large/complex strings to ast.literal_eval(), '
'ast.parse(), compile(), dbm.dumb.open(), eval() or exec() due '
'to stack depth limitations in Python’s AST compiler. Ensure '
'these functions are not used on untrusted data.')
)


if six.PY2:
@test.checks('Exec')
@test.test_id('B326')
def ast_overflow(context):
return ast_overflow_exec_issue()
else:
@test.checks('Call')
@test.test_id('B326')
def ast_overflow(context):
if context.call_function_name_qual == 'exec':
return ast_overflow_exec_issue()
16 changes: 16 additions & 0 deletions examples/ast_overflow-py2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ast
import dbm.dumb


ast.literal_eval('x = 2 + 2')

ast.parse('x = 2 + 2')

compile('2 + 2', '?', 'eval')

dbm.dumb.open('test.db')

eval('2 + 2')

exec('2 + 2')
exec '2 + 2'
15 changes: 15 additions & 0 deletions examples/ast_overflow-py3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ast
import dbm.dumb


ast.literal_eval('x = 2 + 2')

ast.parse('x = 2 + 2')

compile('2 + 2', '?', 'eval')

dbm.dumb.open('test.db')

eval('2 + 2')

exec('2 + 2')
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ bandit.plugins =
# bandit/plugins/ssh_no_host_key_verification.py
ssh_no_host_key_verification = bandit.plugins.ssh_no_host_key_verification:ssh_no_host_key_verification

# bandit/plugins/ast_overflow_exec.py
ast_overflow = bandit.plugins.ast_overflow_exec:ast_overflow

[build_sphinx]
all_files = 1
build-dir = doc/build
Expand Down
27 changes: 27 additions & 0 deletions tests/functional/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ def test_exec(self):
'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0,
'HIGH': 1}
}
self.b_mgr.b_ts = b_test_set.BanditTestSet(
config=self.b_mgr.b_conf,
profile={'exclude': ['B326']}
)
self.check_example(filename, expect)

def test_hardcoded_passwords(self):
Expand Down Expand Up @@ -263,6 +267,29 @@ def test_tempnam(self):
}
self.check_example('tempnam.py', expect)

def test_ast_overflow(self):
'''Test for functions susceptible to AST stack overflow.'''
filename = 'ast_overflow-{}.py'
if six.PY2:
filename = filename.format('py2')
expect = {
'SEVERITY': {'UNDEFINED': 0, 'LOW': 7, 'MEDIUM': 0, 'HIGH': 0},
'CONFIDENCE': {
'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7}
}
else:
filename = filename.format('py3')
expect = {
'SEVERITY': {'UNDEFINED': 0, 'LOW': 6, 'MEDIUM': 0, 'HIGH': 0},
'CONFIDENCE': {
'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 6}
}
self.b_mgr.b_ts = b_test_set.BanditTestSet(
config=self.b_mgr.b_conf,
profile={'exclude': ['B102', 'B307']}
)
self.check_example(filename, expect)

def test_nonsense(self):
'''Test that a syntactically invalid module is skipped.'''
self.run_example('nonsense.py')
Expand Down