[Lldb-commits] [PATCH] D69532: [LLDB][PythonFile] fix dangerous borrow semantics on python2

Lawrence D'Anna via Phabricator via lldb-commits lldb-commits at lists.llvm.org
Mon Oct 28 14:06:36 PDT 2019


lawrence_danna created this revision.
lawrence_danna added reviewers: labath, mgorny.
Herald added a project: LLDB.

It is, I think, inherently unsafe to hand a borrowed native file 
object to a python program on python 2.   This is because the python 
file object must be created with a FILE*, not a descriptor.     Even 
holding on to such a reference past its legal scope can result in
use-after-free violations.   Pytyhon programs are not prepared to deal
with that kind of requirement.

Python 3 does not suffer from this issue.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D69532

Files:
  lldb/include/lldb/Host/File.h
  lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py
  lldb/source/Host/common/File.cpp
  lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp


Index: lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
+++ lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
@@ -1502,19 +1502,28 @@
   file_obj = PyFile_FromFd(file.GetDescriptor(), nullptr, mode, -1, nullptr,
                            "ignore", nullptr, 0);
 #else
-  // We pass ::flush instead of ::fclose here so we borrow the FILE* --
-  // the lldb_private::File still owns it.   NetBSD does not allow
-  // input files to be flushed, so we have to check for that case too.
-  int (*closer)(FILE *);
-  auto opts = file.GetOptions();
+  // It's more or less safe to let a python program borrow a file descriptor.
+  // If they let it dangle and then use it, they'll probably get an exception.
+  // The worst that can happen is they'll wind up doing IO on the wrong
+  // descriptor.   But it would be very unsafe to let a python program borrow
+  // a FILE*.   They could actually crash the program just by keeping a
+  // reference to it around.    Since in python 2 we must have a FILE* and
+  // not a descriptor, we dup the descriptor and fdopen a new FILE* to it
+  // so python can have something it can own safely.
+  auto opts = File::GetOptionsFromMode(mode);
   if (!opts)
     return opts.takeError();
-  if (opts.get() & File::eOpenOptionWrite)
-    closer = ::fflush;
-  else
-    closer = [](FILE *) { return 0; };
-  file_obj = PyFile_FromFile(file.GetStream(), py2_const_cast(""),
-                             py2_const_cast(mode), closer);
+  int fd = file.GetDescriptor();
+  if (!File::DescriptorIsValid(fd))
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "File has no file descriptor");
+  NativeFile dupfile(fd, opts.get(), false);
+  FILE *stream = NativeFile::ReleaseFILE(std::move(dupfile));
+  if (!stream)
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "could not create FILE* stream");
+  file_obj = PyFile_FromFile(stream, py2_const_cast(""), py2_const_cast(mode),
+                             ::fclose);
 #endif
 
   if (!file_obj)
Index: lldb/source/Host/common/File.cpp
===================================================================
--- lldb/source/Host/common/File.cpp
+++ lldb/source/Host/common/File.cpp
@@ -304,6 +304,18 @@
   return m_stream;
 }
 
+FILE *NativeFile::ReleaseFILE(NativeFile &&file) {
+  FILE *stream = nullptr;
+  file.GetStream();
+  if (file.m_own_stream) {
+    stream = file.m_stream;
+    file.m_own_stream = false;
+    file.m_stream = nullptr;
+  }
+  file.Close();
+  return stream;
+}
+
 Status NativeFile::Close() {
   Status error;
   if (StreamIsValid()) {
Index: lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py
+++ lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py
@@ -851,10 +851,6 @@
                     yield sbf
                     sbf.Write(str(i).encode('ascii') + b"\n")
             files = list(i(sbf))
-            # delete them in reverse order, again because each is a borrow
-            # of the previous.
-            while files:
-                files.pop()
         with open(self.out_filename, 'r') as f:
             self.assertEqual(list(range(10)), list(map(int, f.read().strip().split())))
 
Index: lldb/include/lldb/Host/File.h
===================================================================
--- lldb/include/lldb/Host/File.h
+++ lldb/include/lldb/Host/File.h
@@ -411,6 +411,8 @@
   size_t PrintfVarArg(const char *format, va_list args) override;
   llvm::Expected<OpenOptions> GetOptions() const override;
 
+  static FILE *ReleaseFILE(NativeFile &&file);
+
   static char ID;
   virtual bool isA(const void *classID) const override {
     return classID == &ID || File::isA(classID);


-------------- next part --------------
A non-text attachment was scrubbed...
Name: D69532.226748.patch
Type: text/x-patch
Size: 4091 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/lldb-commits/attachments/20191028/133ead4d/attachment.bin>


More information about the lldb-commits mailing list