-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The mktemp function is vulnerable to a race condition where there is a window where the file could be created in between when the filename was created and the file is opened. The solution is to use NamedTemporaryFile as an atomic operation. Signed-off-by: Eric Brown <[email protected]>
- Loading branch information
Showing
13 changed files
with
388 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,7 @@ | ||
import getpass | ||
import imaplib | ||
import os | ||
import tempfile | ||
|
||
|
||
M = imaplib.IMAP4() | ||
|
||
#M.starttls() | ||
|
||
M.login(getpass.getuser(), getpass.getpass()) | ||
M.select() | ||
typ, data = M.search(None, 'ALL') | ||
for num in data[0].split(): | ||
typ, data = M.fetch(num, '(RFC822)') | ||
print('Message %s\n%s\n' % (num, data[0][1])) | ||
M.close() | ||
M.logout() | ||
filename = tempfile.mktemp('', 'tmp', None) | ||
with os.open(filename, "w+", buffering=-1, encoding=None, errors=None, newline=None) as f: | ||
f.write(b"Hello World!\n") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
precli/rules/python/stdlib/tempfile/mktemp_race_condition.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
# Copyright 2023 Secure Saurce LLC | ||
r""" | ||
============================================== | ||
Insecure Temporary File in the Tempfile Module | ||
============================================== | ||
The tempfile.mktemp function in Python is a legacy method for creating | ||
temporary files with a unique name. It is important to note that this function | ||
is susceptible to race conditions, which can occur when multiple processes or | ||
threads attempt to create temporary files concurrently. These race conditions | ||
may lead to unintended behavior, data corruption, or security vulnerabilities | ||
in your code. | ||
------- | ||
Example | ||
------- | ||
.. code-block:: python | ||
:linenos: | ||
:emphasize-lines: 4 | ||
import tempfile | ||
filename = tempfile.mktemp(suffix='', prefix='tmp', dir=None) | ||
with open(filename) as f: | ||
f.write(b"Hello World!\n") | ||
----------- | ||
Remediation | ||
----------- | ||
To ensure the reliability and security of your temporary file management, | ||
consider using NamedTemporaryFile. The tempfile.NamedTemporaryFile class | ||
automatically handles the generation of unique filenames, proper file closure, | ||
and cleanup when the file is no longer needed. | ||
.. code-block:: python | ||
:linenos: | ||
:emphasize-lines: 4 | ||
import tempfile | ||
with tempfile.NamedTemporaryFile(delete=False) as f: | ||
f.write(b"Hello World!\n") | ||
.. seealso:: | ||
- `tempfile — Generate temporary files and directories <https://docs.python.org/3/library/tempfile.html#tempfile.mktemp>`_ | ||
- `CWE-377: Insecure Temporary File <https://cwe.mitre.org/data/definitions/377.html>`_ | ||
.. versionadded:: 1.0.0 | ||
""" # noqa: E501 | ||
from precli.core.fix import Fix | ||
from precli.core.location import Location | ||
from precli.core.result import Result | ||
from precli.rules import Rule | ||
|
||
|
||
class MktempRaceCondition(Rule): | ||
def __init__(self, id: str): | ||
super().__init__( | ||
id=id, | ||
name="insecure_temporary_file", | ||
full_descr=__doc__, | ||
cwe_id=377, | ||
message="The function '{}' can allow insecure ways of creating " | ||
"temporary files and directories that can lead to race " | ||
"conditions.", | ||
targets=("call"), | ||
wildcards={ | ||
"os.*": [ | ||
"open", | ||
], | ||
"tempfile.*": [ | ||
"mktemp", | ||
], | ||
}, | ||
) | ||
|
||
def analyze(self, context: dict, **kwargs: dict) -> Result: | ||
call = kwargs.get("call") | ||
|
||
# TODO: the builtin open can't be found because not imported | ||
if call.name_qualified in ["open"]: | ||
file_arg = call.get_argument(position=0, name="file") | ||
|
||
if ( | ||
file_arg.node.type == "identifier" | ||
and file_arg.value == "tempfile.mktemp" | ||
): | ||
""" | ||
open( | ||
file, | ||
mode='r', | ||
buffering=-1, | ||
encoding=None, | ||
errors=None, | ||
newline=None, | ||
closefd=True, | ||
opener=None | ||
) | ||
""" | ||
arg_list = [] | ||
mode = call.get_argument(position=1, name="mode").node | ||
mode = mode.text.decode() if mode is not None else '"r"' | ||
arg_list.append(f"mode={mode}") | ||
|
||
buff = call.get_argument(position=2, name="buffering").node | ||
if buff is not None: | ||
arg_list.append(f"buffering={buff.text.decode()}") | ||
|
||
enc = call.get_argument(position=3, name="encoding").node | ||
if enc is not None: | ||
arg_list.append(f"encoding={enc.text.decode()}") | ||
|
||
newline = call.get_argument(position=5, name="newline").node | ||
if newline is not None: | ||
arg_list.append(f"newline={newline.text.decode()}") | ||
|
||
# mktemp(suffix='', prefix='tmp', dir=None) | ||
symbol = context["symtab"].get(file_arg.node.text.decode()) | ||
init_call = symbol.call_history[0] | ||
|
||
suffix = init_call.get_argument(position=0, name="suffix").node | ||
if suffix is not None: | ||
arg_list.append(f"suffix={suffix.text.decode()}") | ||
|
||
prefix = init_call.get_argument(position=1, name="prefix").node | ||
if prefix is not None: | ||
arg_list.append(f"prefix={prefix.text.decode()}") | ||
|
||
dirs = init_call.get_argument(position=2, name="dir").node | ||
if dirs is not None: | ||
arg_list.append(f"dir={dirs.text.decode()}") | ||
|
||
arg_list.append("delete=False") | ||
|
||
errors = call.get_argument(position=4, name="errors").node | ||
if errors is not None: | ||
arg_list.append(f"errors={errors.text.decode()}") | ||
arg_str = ", ".join(arg_list) | ||
|
||
""" | ||
NamedTemporaryFile( | ||
mode='w+b', | ||
buffering=-1, | ||
encoding=None, | ||
newline=None, | ||
suffix=None, | ||
prefix=None, | ||
dir=None, | ||
delete=True, | ||
*, | ||
errors=None | ||
) | ||
""" | ||
fixes = [ | ||
Fix( | ||
description="Use the 'NamedTemporaryFile' class to " | ||
"generate a unique filename, do proper file closure, " | ||
"and cleanup.", | ||
deleted_location=Location(node=init_call.node.parent), | ||
inserted_content="", | ||
), | ||
Fix( | ||
description="Use the 'NamedTemporaryFile' class to " | ||
"generate a unique filename, do proper file closure, " | ||
"and cleanup.", | ||
deleted_location=Location(node=call.node), | ||
inserted_content=( | ||
f"tempfile.NamedTemporaryFile({arg_str})" | ||
), | ||
), | ||
] | ||
|
||
return Result( | ||
rule_id=self.id, | ||
location=Location( | ||
file_name=context["file_name"], | ||
node=call.function_node, | ||
), | ||
message=self.message.format(file_arg.value), | ||
fixes=fixes, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
4 changes: 4 additions & 0 deletions
4
tests/unit/rules/python/stdlib/tempfile/examples/tempfile_mktemp.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import tempfile | ||
|
||
|
||
filename = tempfile.mktemp() |
7 changes: 7 additions & 0 deletions
7
tests/unit/rules/python/stdlib/tempfile/examples/tempfile_mktemp_args_open.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import tempfile | ||
|
||
|
||
filename = tempfile.mktemp("", "tmp", dir=None) | ||
f = open(filename, "w+") | ||
f.write(b"Hello World!\n") | ||
f.close() |
8 changes: 8 additions & 0 deletions
8
tests/unit/rules/python/stdlib/tempfile/examples/tempfile_mktemp_args_with_open_args.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import tempfile | ||
|
||
|
||
filename = tempfile.mktemp("", "tmp", dir=None) | ||
with open( | ||
filename, "w+", buffering=-1, encoding=None, errors=None, newline=None | ||
) as f: | ||
f.write(b"Hello World!\n") |
7 changes: 7 additions & 0 deletions
7
tests/unit/rules/python/stdlib/tempfile/examples/tempfile_mktemp_open.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import tempfile | ||
|
||
|
||
filename = tempfile.mktemp() | ||
f = open(filename, "w+") | ||
f.write(b"Hello World!\n") | ||
f.close() |
6 changes: 6 additions & 0 deletions
6
tests/unit/rules/python/stdlib/tempfile/examples/tempfile_mktemp_with_open.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import tempfile | ||
|
||
|
||
filename = tempfile.mktemp() | ||
with open(filename, "w+") as f: | ||
f.write(b"Hello World!\n") |
8 changes: 8 additions & 0 deletions
8
tests/unit/rules/python/stdlib/tempfile/examples/tempfile_mktemp_with_open_multiline.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import tempfile | ||
|
||
|
||
filename = tempfile.mktemp("", "tmp", dir=None) | ||
with open( | ||
filename, "w+", buffering=-1, encoding=None, errors=None, newline=None | ||
) as f: | ||
f.write(b"Hello World!\n") |
Oops, something went wrong.