# Code Injection via eval() on User-Controlled Expression

Language: Python
Severity: Critical
CWE: CWE-95

## Source
1

## Flow
1-4

## Sink
4

## Vulnerable Code
```python
def apply_iot_device_threshold(sensor_id, threshold_expr):
    device_config = {'sensor': sensor_id, 'max_temp': 85, 'min_temp': 15}
    try:
        computed_threshold = eval(threshold_expr, {'__builtins__': {}}, device_config)
        if computed_threshold > 0:
            update_device_registry(sensor_id, computed_threshold)
            return {'status': 'threshold_applied', 'value': computed_threshold}
        return {'status': 'invalid_threshold', 'value': None}
    except Exception as err:
        return {'status': 'error', 'message': str(err)}
```

## Explanation

The function accepts user-controlled input 'threshold_expr' and passes it directly to eval() on line 4. Despite attempting to restrict builtins, this protection is bypassable through various Python techniques such as attribute access chains (e.g., ().__class__.__bases__[0].__subclasses__()), allowing arbitrary code execution.

## Remediation

Replaced the dangerous eval() call with a safe AST-based expression evaluator that only allows numeric constants, predefined variables (max_temp, min_temp), and basic arithmetic operators. The safe_eval_expr function parses the expression into an AST and recursively evaluates only whitelisted node types, preventing any code injection or arbitrary function calls.

## Secure Code
```python
import ast
import operator

ALLOWED_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.FloorDiv: operator.floordiv,
    ast.Mod: operator.mod,
    ast.Pow: operator.pow,
    ast.USub: operator.neg,
    ast.UAdd: operator.pos,
}


def safe_eval_expr(expr, variables):
    """Safely evaluate a mathematical expression with allowed variables."""
    tree = ast.parse(expr, mode='eval')
    return _eval_node(tree.body, variables)


def _eval_node(node, variables):
    if isinstance(node, ast.Expression):
        return _eval_node(node.body, variables)
    elif isinstance(node, ast.Constant):
        if isinstance(node.value, (int, float)):
            return node.value
        raise ValueError(f"Unsupported constant type: {type(node.value).__name__}")
    elif isinstance(node, ast.Name):
        if node.id in variables:
            return variables[node.id]
        raise ValueError(f"Unknown variable: {node.id}")
    elif isinstance(node, ast.BinOp):
        op_type = type(node.op)
        if op_type not in ALLOWED_OPERATORS:
            raise ValueError(f"Unsupported operator: {op_type.__name__}")
        left = _eval_node(node.left, variables)
        right = _eval_node(node.right, variables)
        if op_type == ast.Pow and right > 100:
            raise ValueError("Exponent too large")
        return ALLOWED_OPERATORS[op_type](left, right)
    elif isinstance(node, ast.UnaryOp):
        op_type = type(node.op)
        if op_type not in ALLOWED_OPERATORS:
            raise ValueError(f"Unsupported unary operator: {op_type.__name__}")
        operand = _eval_node(node.operand, variables)
        return ALLOWED_OPERATORS[op_type](operand)
    else:
        raise ValueError(f"Unsupported expression type: {type(node).__name__}")


def apply_iot_device_threshold(sensor_id, threshold_expr):
    device_config = {'sensor': sensor_id, 'max_temp': 85, 'min_temp': 15}
    allowed_variables = {'max_temp': device_config['max_temp'], 'min_temp': device_config['min_temp']}
    try:
        computed_threshold = safe_eval_expr(threshold_expr, allowed_variables)
        if computed_threshold > 0:
            update_device_registry(sensor_id, computed_threshold)
            return {'status': 'threshold_applied', 'value': computed_threshold}
        return {'status': 'invalid_threshold', 'value': None}
    except Exception as err:
        return {'status': 'error', 'message': str(err)}
```
