From d6c89621a18e0e03bccddc7fb564fc62fc15878f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 06:29:36 +0000 Subject: [PATCH] fix(security): replace unsafe eval with restricted AST evaluator in qiskit compiler The _free_pi function used eval() to process parameter strings from QASM, creating a remote code execution vulnerability. This change introduces a restricted _safe_eval function that only allows basic arithmetic and tuples, significantly improving the security posture of the compiler. Co-authored-by: refraction-ray <35157286+refraction-ray@users.noreply.github.com> --- tensorcircuit/compiler/qiskit_compiler.py | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tensorcircuit/compiler/qiskit_compiler.py b/tensorcircuit/compiler/qiskit_compiler.py index dfde2f3a..4e4fe2b3 100644 --- a/tensorcircuit/compiler/qiskit_compiler.py +++ b/tensorcircuit/compiler/qiskit_compiler.py @@ -2,6 +2,7 @@ compiler interface via qiskit """ +import ast import re from typing import Any, Dict, Optional @@ -10,6 +11,35 @@ from ..translation import qiskit_from_qasm_str_ordered_measure, get_qiskit_qasm +def _safe_eval(s: str) -> Any: + operators = { + ast.Add: lambda a, b: a + b, + ast.Sub: lambda a, b: a - b, + ast.Mult: lambda a, b: a * b, + ast.Div: lambda a, b: a / b, + ast.Pow: lambda a, b: a ** b, + ast.USub: lambda a: -a, + ast.UAdd: lambda a: a, + } + + def _eval(node: Any) -> Any: + if isinstance(node, ast.Expression): + return _eval(node.body) + if isinstance(node, ast.Constant): + return node.value + if isinstance(node, ast.Num): # python < 3.8 + return node.n + if isinstance(node, ast.BinOp): + return operators[type(node.op)](_eval(node.left), _eval(node.right)) + if isinstance(node, ast.UnaryOp): + return operators[type(node.op)](_eval(node.operand)) + if isinstance(node, ast.Tuple): + return tuple(_eval(n) for n in node.elts) + raise ValueError(f"Unsupported node type: {type(node)}") + + return _eval(ast.parse(s, mode="eval")) + + def _free_pi(s: str) -> str: # dirty trick to get rid of pi in openqasm from qiskit rs = [] @@ -21,7 +51,7 @@ def _free_pi(s: str) -> str: rs.append(r) else: v = r[inc.start() : inc.end()] - v = eval(v) + v = _safe_eval(v) if not isinstance(v, tuple): r = r[: inc.start()] + "(" + str(v) + ")" + r[inc.end() :] else: # u gate case