[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Bug#1024903: pytorch: CVE-2022-45907: torch.jit.annotations.parse_type_line prone to command injection



Hi,

Turning upstream commit 767f6aa49fe20a2766b9843d01e3b7f7793df6a3 into a
patch seems straightforward on 1.12.1-1 and 1.7.1-7. At least both
versions still build fine with the patch. I will see if I can get around
to running the test soon enough.

Meanwhile, attached are the two corresponding patches.

I don't know if this is worth fixing, since upstream's comments seem to
indicate that the code in question is only for Python 2
compatibility.


 Best,
 Gard
 
From: Gard Spreemann <gspr@nonempty.org>
Date: Tue, 29 Nov 2022 17:51:18 +0100
Subject: Do not blindly eval input string

This fix for CVE-2022-45907 is based on upstream commit
767f6aa49fe20a2766b9843d01e3b7f7793df6a3 , whose message follows.

---

commit 767f6aa49fe20a2766b9843d01e3b7f7793df6a3
Author: Nikita Shulga <nshulga@meta.com>
Date:   Thu Nov 17 22:05:27 2022 +0000

    [JIT][Security] Do not blindly eval input string (#89189)

    Introduce `_eval_no_call` method, that evaluates statement only if it
    does not contain any calls(done by examining the bytecode), thus preventing command injection exploit

    Added simple unit test to check for that
    `torch.jit.annotations.get_signature` would not result in calling random
    code.

    Although, this code path exists for Python-2 compatibility, and perhaps
    should be simply removed.

    Fixes https://github.com/pytorch/pytorch/issues/88868

    Pull Request resolved: https://github.com/pytorch/pytorch/pull/89189
    Approved by: https://github.com/suo
---
 test/test_jit.py                               |  8 ++++++++
 torch/csrc/jit/frontend/script_type_parser.cpp |  2 +-
 torch/jit/annotations.py                       | 14 ++++++++++++--
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/test/test_jit.py b/test/test_jit.py
index fb9f6fa..49b51dd 100644
--- a/test/test_jit.py
+++ b/test/test_jit.py
@@ -3422,6 +3422,14 @@ def foo(x):
                 return a + 2
             torch.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)
diff --git a/torch/csrc/jit/frontend/script_type_parser.cpp b/torch/csrc/jit/frontend/script_type_parser.cpp
index bec9e87..2e014c9 100644
--- a/torch/csrc/jit/frontend/script_type_parser.cpp
+++ b/torch/csrc/jit/frontend/script_type_parser.cpp
@@ -229,7 +229,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,
diff --git a/torch/jit/annotations.py b/torch/jit/annotations.py
index ba68920..bb09f96 100644
--- a/torch/jit/annotations.py
+++ b/torch/jit/annotations.py
@@ -1,4 +1,5 @@
 import ast
+import dis
 import enum
 import inspect
 import re
@@ -135,6 +136,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.
 
@@ -145,7 +155,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 # 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
 
@@ -153,7 +163,7 @@ def parse_type_line(type_line, rcb, loc):
         arg_ann = (arg_ann,)
 
     try:
-        ret_ann = eval(ret_ann_str, {}, EvalEnv(rcb))  # type: ignore # 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
 
From: Gard Spreemann <gspr@nonempty.org>
Date: Tue, 29 Nov 2022 17:17:50 +0100
Subject: Do not blindly eval input string

This fix for CVE-2022-45907 is based on upstream commit
767f6aa49fe20a2766b9843d01e3b7f7793df6a3 , whose message follows.

---

commit 767f6aa49fe20a2766b9843d01e3b7f7793df6a3
Author: Nikita Shulga <nshulga@meta.com>
Date:   Thu Nov 17 22:05:27 2022 +0000

    [JIT][Security] Do not blindly eval input string (#89189)

    Introduce `_eval_no_call` method, that evaluates statement only if it
    does not contain any calls(done by examining the bytecode), thus preventing command injection exploit

    Added simple unit test to check for that
    `torch.jit.annotations.get_signature` would not result in calling random
    code.

    Although, this code path exists for Python-2 compatibility, and perhaps
    should be simply removed.

    Fixes https://github.com/pytorch/pytorch/issues/88868

    Pull Request resolved: https://github.com/pytorch/pytorch/pull/89189
    Approved by: https://github.com/suo
---
 test/test_jit.py                               |  8 ++++++++
 torch/csrc/jit/frontend/script_type_parser.cpp |  2 +-
 torch/jit/annotations.py                       | 14 ++++++++++++--
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/test/test_jit.py b/test/test_jit.py
index 7dbae13..d152f4f 100644
--- a/test/test_jit.py
+++ b/test/test_jit.py
@@ -3911,6 +3911,14 @@ def foo(x):
                 return a + 2
             torch.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)
diff --git a/torch/csrc/jit/frontend/script_type_parser.cpp b/torch/csrc/jit/frontend/script_type_parser.cpp
index f5d6f64..d05ec95 100644
--- a/torch/csrc/jit/frontend/script_type_parser.cpp
+++ b/torch/csrc/jit/frontend/script_type_parser.cpp
@@ -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,
diff --git a/torch/jit/annotations.py b/torch/jit/annotations.py
index 45d708c..a12160c 100644
--- a/torch/jit/annotations.py
+++ b/torch/jit/annotations.py
@@ -1,4 +1,5 @@
 import ast
+import dis
 import enum
 import inspect
 import 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
 

Attachment: signature.asc
Description: PGP signature


Reply to: