PyTorch vulnerable to arbitrary code execution
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
0 → fixed in 1.13.1
Details
In PyTorch before trunk/89695, torch.jit.annotations.parse_type_line can cause arbitrary code execution because eval is used unsafely. The fix for this issue is available in version 1.13.1. There is a release checker in [issue #89855](https://github.com/pytorch/pytorch/issues/89855).
The fix
[JIT][Security] Do not blindly eval input string
test/test_jit.py+8 −0
@@ -3951,6 +3951,14 @@ def invalid4(a):return a + 2torch.jit.script(invalid4)+def test_calls_in_type_annotations(self):+with self.assertRaisesRegex(RuntimeError, "Type annotation should not contain calls"):+def spooky(a):+# type: print("Hello") -> Tensor+return a + 2+print(torch.__file__)+torch.jit.annotations.get_signature(spooky, None, 1, True)+def test_is_optional(self):ann = Union[List[int], List[float]]torch._jit_internal.is_optional(ann)
torch/csrc/jit/frontend/script_type_parser.cpp+1 −1
@@ -316,7 +316,7 @@ std::vector<IValue> ScriptTypeParser::evaluateDefaults(// We then run constant prop on this graph and check the results are// constant. This approach avoids having to have separate handling of// default arguments from standard expressions by piecing together existing-// machinery for graph generation, constant propgation, and constant+// machinery for graph generation, constant propagation, and constant// extraction.auto tuple_type = Subscript::create(r,
torch/jit/annotations.py+12 −2
@@ -1,4 +1,5 @@import ast+import disimport enumimport inspectimport re@@ -144,6 +145,15 @@ def check_fn(fn, loc):raise torch.jit.frontend.FrontendError(loc, "Expected a single top-level function")+def _eval_no_call(stmt, glob, loc):+"""Evaluate statement as long as it does not contain any method/function calls"""+bytecode = compile(stmt, "", mode="eval")+for insn in dis.get_instructions(bytecode):+if "CALL" in insn.opname:+raise RuntimeError(f"Type annotation should not contain calls, but '{stmt}' does")+return eval(bytecode, glob, loc) # type: ignore[arg-type] # noqa: P204++def parse_type_line(type_line, rcb, loc):"""Parses a type annotation specified as a comment.@@ -154,7 +164,7 @@ def parse_type_line(type_line, rcb, loc):arg_ann_str, ret_ann_str = split_type_line(type_line)try:-arg_ann = eval(arg_ann_str, {}, EvalEnv(rcb)) # type: ignore[arg-type] # noqa: P204+arg_ann = _eval_no_call(arg_ann_str, {}, EvalEnv(rcb))except (NameError, SyntaxError) as e:raise RuntimeError("Failed to parse the argument list of a type annotation") from e@@ -162,7 +172,7 @@ def parse_type_line(type_line, rcb, loc):arg_ann = (arg_ann,)try:-ret_ann = eval(ret_ann_str, {}, EvalEnv(rcb)) # type: ignore[arg-type] # noqa: P204+ret_ann = _eval_no_call(ret_ann_str, {}, EvalEnv(rcb))except (NameError, SyntaxError) as e:raise RuntimeError("Failed to parse the return type of a type annotation") from etest/test_jit.py | 2 +-1 file changed, 1 insertion(+), 1 deletion(-)
test/test_jit.py+1 −1
@@ -3954,7 +3954,7 @@ def invalid4(a):def test_calls_in_type_annotations(self):with self.assertRaisesRegex(RuntimeError, "Type annotation should not contain calls"):def spooky(a):-# type: print("Hello") -> Tensor+# type: print("Hello") -> Tensor # noqa: F723return a + 2print(torch.__file__)torch.jit.annotations.get_signature(spooky, None, 1, True)
[JIT][Security] Do not blindly eval input string (#89189)
test/test_jit.py+8 −0
@@ -3951,6 +3951,14 @@ def invalid4(a):return a + 2torch.jit.script(invalid4)+def test_calls_in_type_annotations(self):+with self.assertRaisesRegex(RuntimeError, "Type annotation should not contain calls"):+def spooky(a):+# type: print("Hello") -> Tensor # noqa: F723+return a + 2+print(torch.__file__)+torch.jit.annotations.get_signature(spooky, None, 1, True)+def test_is_optional(self):ann = Union[List[int], List[float]]torch._jit_internal.is_optional(ann)
torch/csrc/jit/frontend/script_type_parser.cpp+1 −1
@@ -316,7 +316,7 @@ std::vector<IValue> ScriptTypeParser::evaluateDefaults(// We then run constant prop on this graph and check the results are// constant. This approach avoids having to have separate handling of// default arguments from standard expressions by piecing together existing-// machinery for graph generation, constant propgation, and constant+// machinery for graph generation, constant propagation, and constant// extraction.auto tuple_type = Subscript::create(r,
torch/jit/annotations.py+12 −2
@@ -1,4 +1,5 @@import ast+import disimport enumimport inspectimport re@@ -144,6 +145,15 @@ def check_fn(fn, loc):raise torch.jit.frontend.FrontendError(loc, "Expected a single top-level function")+def _eval_no_call(stmt, glob, loc):+"""Evaluate statement as long as it does not contain any method/function calls"""+bytecode = compile(stmt, "", mode="eval")+for insn in dis.get_instructions(bytecode):+if "CALL" in insn.opname:+raise RuntimeError(f"Type annotation should not contain calls, but '{stmt}' does")+return eval(bytecode, glob, loc) # type: ignore[arg-type] # noqa: P204++def parse_type_line(type_line, rcb, loc):"""Parses a type annotation specified as a comment.@@ -154,7 +164,7 @@ def parse_type_line(type_line, rcb, loc):arg_ann_str, ret_ann_str = split_type_line(type_line)try:-arg_ann = eval(arg_ann_str, {}, EvalEnv(rcb)) # type: ignore[arg-type] # noqa: P204+arg_ann = _eval_no_call(arg_ann_str, {}, EvalEnv(rcb))except (NameError, SyntaxError) as e:raise RuntimeError("Failed to parse the argument list of a type annotation") from e@@ -162,7 +172,7 @@ def parse_type_line(type_line, rcb, loc):arg_ann = (arg_ann,)try:-ret_ann = eval(ret_ann_str, {}, EvalEnv(rcb)) # type: ignore[arg-type] # noqa: P204+ret_ann = _eval_no_call(ret_ann_str, {}, EvalEnv(rcb))except (NameError, SyntaxError) as e:raise RuntimeError("Failed to parse the return type of a type annotation") from e
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2022-45907
- WEBhttps://github.com/pytorch/pytorch/issues/88868
- WEBhttps://github.com/pytorch/pytorch/issues/89855
- WEBhttps://github.com/pytorch/pytorch/pull/89189
- WEBhttps://github.com/pytorch/pytorch/commit/767f6aa49fe20a2766b9843d01e3b7f7793df6a3
- WEBhttps://github.com/pypa/advisory-database/tree/main/vulns/torch/PYSEC-2022-43015.yaml
- PACKAGEhttps://github.com/pytorch/pytorch
- WEBhttps://github.com/pytorch/pytorch/releases/tag/v1.13.1