Replace eval with function matching
Using eval in code is not safe and may lead to security risks, especially given that query itself is supplied by a user. This refactors the code making is presumably safe and prone to code injections, which are possible with eval. Story: 2011539 Task: 52866 Change-Id: If629023052aa2c067c419bba10837f77bcc3e59c Signed-off-by: Dmitriy Rabotyagov <dmitriy@adria-cloud.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
security:
|
||||||
|
- |
|
||||||
|
A security issue in the entity graph querying mechanism has been fixed.
|
||||||
|
This change hardens the query parser against malicious input.
|
@@ -12,6 +12,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import operator
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from vitrage.common.exception import VitrageError
|
from vitrage.common.exception import VitrageError
|
||||||
@@ -21,13 +22,21 @@ LOG = logging.getLogger(__name__)
|
|||||||
operators = [
|
operators = [
|
||||||
'<',
|
'<',
|
||||||
'<=',
|
'<=',
|
||||||
# '=',
|
|
||||||
'==',
|
'==',
|
||||||
'!=',
|
'!=',
|
||||||
'>=',
|
'>=',
|
||||||
'>',
|
'>',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ops = {
|
||||||
|
'<': operator.lt,
|
||||||
|
'<=': operator.le,
|
||||||
|
'==': operator.eq,
|
||||||
|
'!=': operator.ne,
|
||||||
|
'>=': operator.ge,
|
||||||
|
'>': operator.gt,
|
||||||
|
}
|
||||||
|
|
||||||
logical_operations = [
|
logical_operations = [
|
||||||
'and',
|
'and',
|
||||||
'or'
|
'or'
|
||||||
@@ -64,10 +73,7 @@ def create_predicate(query_dict):
|
|||||||
:return: a predicate "match(item)"
|
:return: a predicate "match(item)"
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
expression = _create_query_expression(query=query_dict)
|
return _create_query_function(query=query_dict)
|
||||||
LOG.debug('create_predicate::%s', expression)
|
|
||||||
expression = 'lambda item: ' + expression
|
|
||||||
return eval(expression)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error('invalid query format %s. Exception: %s',
|
LOG.error('invalid query format %s. Exception: %s',
|
||||||
query_dict, e)
|
query_dict, e)
|
||||||
@@ -75,46 +81,42 @@ def create_predicate(query_dict):
|
|||||||
query_dict, e)
|
query_dict, e)
|
||||||
|
|
||||||
|
|
||||||
def _create_query_expression(query, parent_operator=None):
|
def _create_query_function(query, parent_operator=None):
|
||||||
expressions = []
|
|
||||||
|
|
||||||
# First element or element under logical operation
|
# First element or element under logical operation
|
||||||
if not parent_operator and isinstance(query, dict):
|
if not parent_operator and isinstance(query, dict):
|
||||||
(key, value) = query.copy().popitem()
|
(key, value) = query.copy().popitem()
|
||||||
return _create_query_expression(value, key)
|
return _create_query_function(value, key)
|
||||||
|
|
||||||
# Continue recursion on logical (and/or) operation
|
# Continue recursion on logical (and/or) operation
|
||||||
elif parent_operator in logical_operations and isinstance(query, list):
|
elif parent_operator in logical_operations and isinstance(query, list):
|
||||||
for val in query:
|
predicates = [_create_query_function(val) for val in query]
|
||||||
expressions.append(_create_query_expression(val))
|
|
||||||
return _join_logical_operator(parent_operator, expressions)
|
if not predicates:
|
||||||
|
return lambda item: False
|
||||||
|
|
||||||
|
if parent_operator == 'and':
|
||||||
|
return lambda item: all(p(item) for p in predicates)
|
||||||
|
elif parent_operator == 'or':
|
||||||
|
return lambda item: any(p(item) for p in predicates)
|
||||||
|
|
||||||
# Recursion evaluate leaf (stop condition)
|
# Recursion evaluate leaf (stop condition)
|
||||||
elif parent_operator in operators:
|
elif parent_operator in operators:
|
||||||
for key, val in query.items():
|
predicates = []
|
||||||
expressions.append('item.get(' + _evaluable_str(key) + ')' +
|
op_func = ops[parent_operator]
|
||||||
parent_operator + ' ' + _evaluable_str(val))
|
for field, value in query.items():
|
||||||
return _join_logical_operator('and', expressions)
|
predicates.append(
|
||||||
|
lambda item, f=field, v=value: op_func(item.get(f), v)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Multiple conditions under a comparison operator are implicitly 'and'
|
||||||
|
if len(predicates) > 1:
|
||||||
|
return lambda item: all(p(item) for p in predicates)
|
||||||
|
elif predicates:
|
||||||
|
return predicates[0]
|
||||||
|
else:
|
||||||
|
return lambda item: False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise VitrageError('invalid partial query format',
|
raise VitrageError('invalid partial query format',
|
||||||
parent_operator, query)
|
parent_operator, query)
|
||||||
|
|
||||||
|
|
||||||
def _evaluable_str(value):
|
|
||||||
"""wrap string/unicode with back tick"""
|
|
||||||
if isinstance(value, str):
|
|
||||||
return '\'' + value + '\''
|
|
||||||
else:
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
|
|
||||||
def _join_logical_operator(op, expressions):
|
|
||||||
"""Create an expressions string
|
|
||||||
|
|
||||||
Example input:
|
|
||||||
op='AND'
|
|
||||||
expressions=['a == b', 'c < d']
|
|
||||||
Example output: (a == b AND c < d)
|
|
||||||
"""
|
|
||||||
separator = ' ' + op + ' '
|
|
||||||
return '(' + separator.join(expressions) + ')'
|
|
||||||
|
Reference in New Issue
Block a user