Skip to content

Commit

Permalink
Merge pull request #173 from kevin-bates/tolerate-owner-x-bit
Browse files Browse the repository at this point in the history
Tolerate execute bit on owner when writing file
  • Loading branch information
MSeal authored Oct 24, 2019
2 parents 9a1ddcf + 54fba6c commit 5a3c69c
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 8 deletions.
33 changes: 27 additions & 6 deletions jupyter_core/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,17 +374,37 @@ def win32_restrict_file_to_user(fname):
sd.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(fname, win32security.DACL_SECURITY_INFORMATION, sd)


def get_file_mode(fname):
"""Retrieves the file mode corresponding to fname in a filesystem-tolerant manner.
Parameters
----------
fname : unicode
The path to the file to get mode from
"""
# Some filesystems (e.g., CIFS) auto-enable the execute bit on files. As a result, we
# should tolerate the execute bit on the file's owner when validating permissions - thus
# the missing one's bit on the third octet.
return stat.S_IMODE(os.stat(fname).st_mode) & 0o7677 # Use 4 octets since S_IMODE does the same


@contextmanager
def secure_write(fname, binary=False):
"""Opens a file in the most restricted pattern available for
writing content. This limits the file mode to `600` and yields
writing content. This limits the file mode to `0o0600` and yields
the resulting opened filed handle.
Parameters
----------
fname : unicode
The path to the file to write
binary: boolean
Indicates that the file is binary
"""
mode = 'wb' if binary else 'w'
open_flag = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
Expand All @@ -398,16 +418,17 @@ def secure_write(fname, binary=False):
# Python on windows does not respect the group and public bits for chmod, so we need
# to take additional steps to secure the contents.
# Touch file pre-emptively to avoid editing permissions in open files in Windows
fd = os.open(fname, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600)
fd = os.open(fname, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o0600)
os.close(fd)
open_flag = os.O_WRONLY | os.O_TRUNC
win32_restrict_file_to_user(fname)

with os.fdopen(os.open(fname, open_flag, 0o600), mode) as f:
with os.fdopen(os.open(fname, open_flag, 0o0600), mode) as f:
if os.name != 'nt':
# Enforce that the file got the requested permissions before writing
if '0600' != oct(stat.S_IMODE(os.stat(fname).st_mode)).replace('0o', '0'):
file_mode = get_file_mode(fname)
if 0o0600 != file_mode:
raise RuntimeError("Permissions assignment failed for secure file: '{file}'."
"Got '{permissions}' instead of '600'"
.format(file=fname, permissions=os.stat(fname).st_mode))
"Got '{permissions}' instead of '0o0600'"
.format(file=fname, permissions=oct(file_mode)))
yield f
4 changes: 2 additions & 2 deletions jupyter_core/tests/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def test_secure_write_unix():
with secure_write(fname) as f:
f.write('test 1')
mode = os.stat(fname).st_mode
assert '0600' == oct(stat.S_IMODE(mode)).replace('0o', '0')
assert 0o0600 == (stat.S_IMODE(mode) & 0o7677) # tolerate owner-execute bit
with open(fname, 'r') as f:
assert f.read() == 'test 1'

Expand All @@ -299,7 +299,7 @@ def test_secure_write_unix():
with secure_write(fname) as f:
f.write('test 2')
mode = os.stat(fname).st_mode
assert '0600' == oct(stat.S_IMODE(mode)).replace('0o', '0')
assert 0o0600 == (stat.S_IMODE(mode) & 0o7677) # tolerate owner-execute bit
with open(fname, 'r') as f:
assert f.read() == 'test 2'
finally:
Expand Down

0 comments on commit 5a3c69c

Please sign in to comment.