Skip to content

Commit 34564d8

Browse files
thatchgraingertwoodruffw
authored
Attempt to overwrite without race condition (#335)
Co-authored-by: Thomas Grainger <[email protected]> Co-authored-by: William Woodruff <[email protected]>
1 parent f01cfef commit 34564d8

File tree

1 file changed

+10
-47
lines changed

1 file changed

+10
-47
lines changed

cachecontrol/caches/file_cache.py

+10-47
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import hashlib
77
import os
8+
import tempfile
89
from textwrap import dedent
910
from typing import IO, TYPE_CHECKING
1011
from pathlib import Path
@@ -18,47 +19,6 @@
1819
from filelock import BaseFileLock
1920

2021

21-
def _secure_open_write(filename: str, fmode: int) -> IO[bytes]:
22-
# We only want to write to this file, so open it in write only mode
23-
flags = os.O_WRONLY
24-
25-
# os.O_CREAT | os.O_EXCL will fail if the file already exists, so we only
26-
# will open *new* files.
27-
# We specify this because we want to ensure that the mode we pass is the
28-
# mode of the file.
29-
flags |= os.O_CREAT | os.O_EXCL
30-
31-
# Do not follow symlinks to prevent someone from making a symlink that
32-
# we follow and insecurely open a cache file.
33-
if hasattr(os, "O_NOFOLLOW"):
34-
flags |= os.O_NOFOLLOW
35-
36-
# On Windows we'll mark this file as binary
37-
if hasattr(os, "O_BINARY"):
38-
flags |= os.O_BINARY
39-
40-
# Before we open our file, we want to delete any existing file that is
41-
# there
42-
try:
43-
os.remove(filename)
44-
except OSError:
45-
# The file must not exist already, so we can just skip ahead to opening
46-
pass
47-
48-
# Open our file, the use of os.O_CREAT | os.O_EXCL will ensure that if a
49-
# race condition happens between the os.remove and this line, that an
50-
# error will be raised. Because we utilize a lockfile this should only
51-
# happen if someone is attempting to attack us.
52-
fd = os.open(filename, flags, fmode)
53-
try:
54-
return os.fdopen(fd, "wb")
55-
56-
except:
57-
# An error occurred wrapping our FD in a file object
58-
os.close(fd)
59-
raise
60-
61-
6222
class _FileCacheMixin:
6323
"""Shared implementation for both FileCache variants."""
6424

@@ -122,15 +82,18 @@ def _write(self, path: str, data: bytes) -> None:
12282
Safely write the data to the given path.
12383
"""
12484
# Make sure the directory exists
125-
try:
126-
os.makedirs(os.path.dirname(path), self.dirmode)
127-
except OSError:
128-
pass
85+
dirname = os.path.dirname(path)
86+
os.makedirs(dirname, self.dirmode, exist_ok=True)
12987

13088
with self.lock_class(path + ".lock"):
13189
# Write our actual file
132-
with _secure_open_write(path, self.filemode) as fh:
133-
fh.write(data)
90+
(fd, name) = tempfile.mkstemp(dir=dirname)
91+
try:
92+
os.write(fd, data)
93+
finally:
94+
os.close(fd)
95+
os.chmod(name, self.filemode)
96+
os.replace(name, path)
13497

13598
def _delete(self, key: str, suffix: str) -> None:
13699
name = self._fn(key) + suffix

0 commit comments

Comments
 (0)