Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-39318: Catch a failure in NamedTemporaryFile to prevent leaking a descriptor #17985

Open
wants to merge 2 commits into
base: master
from

Conversation

@nedbat
Copy link
Member

nedbat commented Jan 13, 2020

This happened in the real world because someone mocked
_TemporaryFileWrapper to raise an OSError. See
https://nedbatchelder.com/blog/202001/bug_915_solved.html for more
details.

https://bugs.python.org/issue39318

… descriptor

This happened in the real world because someone mocked
_TemporaryFileWrapper to raise an OSError. See
https://nedbatchelder.com/blog/202001/bug_915_solved.html for more
details.
@@ -543,12 +543,18 @@ def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
file = _io.open(fd, mode, buffering=buffering,
newline=newline, encoding=encoding, errors=errors)

return _TemporaryFileWrapper(file, name, delete)
except BaseException:

This comment has been minimized.

Copy link
@albertz

albertz Jan 13, 2020

I would recommend to not use BaseException, but simply Exception here. Otherwise there is still a small race condition, e.g. if there is some KeyboardInterrupt after _io.open already created the file instance. Then you would run exactly in the same problem as before, that os.close is called twice.

This comment has been minimized.

Copy link
@nedbat

nedbat Jan 13, 2020

Author Member

I don't understand the bad scenario you are describing. If there is a KeyboardInterrupt, then BaseException will catch it, and either the first or the second except clause will handle it (depending on exactly when the Ctrl-C happens), and the objects will be freed.

If we change to Exception, then KeyboardInterrupts won't be caught at all, and none of the clean up will happen.

This comment has been minimized.

Copy link
@albertz

albertz Jan 13, 2020

The bytecode will look sth like:

  2           0 SETUP_EXCEPT            14 (to 16)

  3           2 LOAD_GLOBAL              0 (_io)
              4 LOAD_METHOD              1 (open)
              6 LOAD_FAST                0 (fd)
             ... (other args ignored here)
              8 CALL_METHOD              1
             ... assume KeyboardInterrupt is catched exactly here
             10 STORE_FAST               1 (file)
             12 POP_BLOCK
             14 JUMP_FORWARD            30 (to 46)

  4     >>   16 DUP_TOP
             18 LOAD_GLOBAL              2 (BaseException)
             20 COMPARE_OP              10 (exception match)
             22 POP_JUMP_IF_FALSE       44
             24 POP_TOP
             26 POP_TOP
             28 POP_TOP
             ...

It means that the file object already exists, and once the GC cleans it up, that file object will close the fd. However, now our exception handler runs, and also calls os.close(fd) again.

except BaseException:
_os.unlink(name)
_os.close(fd)
raise

try:
return _TemporaryFileWrapper(file, name, delete)
except BaseException:

This comment has been minimized.

Copy link
@albertz

albertz Jan 13, 2020

Also here, better Exception instead of BaseException.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.