@@ -1027,46 +1027,59 @@ def load_bands(path, letter, namespace, base):
10271027namespace.update(safe_funcs)
10281028# Band arrays are full ndarrays, so attribute access (``A.tofile(...)``,
10291029# ``A.dump(...)``) would still write files even with an empty ``__builtins__``
1030- # and no bare ``np``. Parse the expression and reject attribute access plus any
1031- # call that is not one of the curated safe functions before ``eval``.
1030+ # and no bare ``np``. Allowlist the expression AST before ``eval`` so only
1031+ # curated calls/operators run — and so list/bytes/tuple multiplication cannot
1032+ # allocate unbounded memory before the shape check.
10321033try:
10331034 tree = ast.parse(expression, mode="eval")
10341035except SyntaxError as exc:
10351036 raise SystemExit(f"Failed to evaluate expression: {exc}") from exc
10361037allowed_calls = set(safe_funcs)
1038+ _allowed_nodes = (
1039+ ast.Expression,
1040+ ast.Name,
1041+ ast.Load,
1042+ ast.Constant,
1043+ ast.Call,
1044+ ast.BinOp,
1045+ ast.UnaryOp,
1046+ ast.BoolOp,
1047+ ast.Compare,
1048+ ast.IfExp,
1049+ ast.Add,
1050+ ast.Sub,
1051+ ast.Mult,
1052+ ast.Div,
1053+ ast.FloorDiv,
1054+ ast.Mod,
1055+ ast.Pow,
1056+ ast.UAdd,
1057+ ast.USub,
1058+ ast.Not,
1059+ ast.And,
1060+ ast.Or,
1061+ ast.Eq,
1062+ ast.NotEq,
1063+ ast.Lt,
1064+ ast.LtE,
1065+ ast.Gt,
1066+ ast.GtE,
1067+ ast.keyword,
1068+ )
10371069for node in ast.walk(tree):
1038- if isinstance(node, ast.Attribute ):
1070+ if not isinstance(node, _allowed_nodes ):
10391071 raise SystemExit(
1040- "Expression may not access attributes "
1072+ f "Expression may not use {type(node).__name__} "
10411073 "(band math only allows curated functions and operators)"
10421074 )
1043- # Reject containers/comprehensions so expressions like ``[0] * 10**9``
1044- # cannot allocate unbounded memory before the shape check runs.
1045- if isinstance(
1046- node,
1047- (
1048- ast.List,
1049- ast.Dict,
1050- ast.Set,
1051- ast.ListComp,
1052- ast.DictComp,
1053- ast.SetComp,
1054- ast.GeneratorExp,
1055- ),
1056- ):
1075+ if isinstance(node, ast.Constant) and not isinstance(node.value, (int, float, bool)):
10571076 raise SystemExit(
1058- "Expression may not build lists, dicts, or comprehensions "
1077+ "Expression may only use numeric constants "
10591078 "(band math only allows curated functions and operators)"
10601079 )
10611080 if isinstance(node, ast.Call):
10621081 # ``np.where(...)`` / ``A.tofile(...)`` parse as Attribute-backed calls;
1063- # reject them the same way as bare attribute access so ndarray file I/O
1064- # and the withheld ``np`` module stay unreachable.
1065- if isinstance(node.func, ast.Attribute):
1066- raise SystemExit(
1067- "Expression may not access attributes "
1068- "(band math only allows curated functions and operators)"
1069- )
1082+ # Attribute is already rejected above, but keep an explicit Name check.
10701083 if not isinstance(node.func, ast.Name) or node.func.id not in allowed_calls:
10711084 name = node.func.id if isinstance(node.func, ast.Name) else type(node.func).__name__
10721085 raise SystemExit(f"Call to '{name}' is not allowed in band math")
0 commit comments