CVE-2014-1858: Improper Input Validation (CWE-20)
value: __init__.py in f2py in NumPy before 1.8.1 allows local users to write to arbitrary files via a symlink attack on a temporary file.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 5.5
Published: 2018-01-08T19:29Z
Repository: https://github.com/numpy/numpy
Language: Python
Files changed: numpy/core/tests/test_memmap.py, numpy/core/tests/test_multiarray.py, numpy/f2py/__init__.py, numpy/f2py/f2py2e.py, numpy/lib/tests/test_io.py
Commit date: 2014-02-06 00:54:24 +0100
Commit message: ENH: remove insecure mktemp use

mktemp only returns a filename, a malicous user could replace it before
it gets used.

Diff: diff --git a/numpy/core/tests/test_memmap.py b/numpy/core/tests/test_memmap.py
index 6de6319ef..10e7a0817 100644
--- a/numpy/core/tests/test_memmap.py
+++ b/numpy/core/tests/test_memmap.py
@@ -1,9 +1,9 @@
 from __future__ import division, absolute_import, print_function
 
 import sys
-from tempfile import NamedTemporaryFile, TemporaryFile, mktemp
+from tempfile import NamedTemporaryFile, TemporaryFile
 import os
 
 from numpy import memmap
 from numpy import arange, allclose, asarray
 from numpy.testing import *
@@ -31,16 +31,15 @@ def test_roundtrip(self):
                        shape=self.shape)
         assert_(allclose(self.data, newfp))
         assert_array_equal(self.data, newfp)
 
     def test_open_with_filename(self):
-        tmpname = mktemp('', 'mmap')
-        fp = memmap(tmpname, dtype=self.dtype, mode='w+',
-                       shape=self.shape)
-        fp[:] = self.data[:]
-        del fp
-        os.unlink(tmpname)
+        with NamedTemporaryFile() as tmp:
+            fp = memmap(tmp.name, dtype=self.dtype, mode='w+',
+                        shape=self.shape)
+            fp[:] = self.data[:]
+            del fp
 
     def test_unnamed_file(self):
         with TemporaryFile() as f:
             fp = memmap(f, dtype=self.dtype, shape=self.shape)
             del fp
@@ -53,21 +52,20 @@ def test_attributes(self):
         self.assertEqual(offset, fp.offset)
         self.assertEqual(mode, fp.mode)
         del fp
 
     def test_filename(self):
-        tmpname = mktemp('', 'mmap')
-        fp = memmap(tmpname, dtype=self.dtype, mode='w+',
-                       shape=self.shape)
-        abspath = os.path.abspath(tmpname)
-        fp[:] = self.data[:]
-        self.assertEqual(abspath, fp.filename)
-        b = fp[:1]
-        self.assertEqual(abspath, b.filename)
-        del b
-        del fp
-        os.unlink(tmpname)
+        with NamedTemporaryFile() as tmp:
+            fp = memmap(tmp.name, dtype=self.dtype, mode='w+',
+                        shape=self.shape)
+            abspath = os.path.abspath(tmp.name)
+            fp[:] = self.data[:]
+            self.assertEqual(abspath, fp.filename)
+            b = fp[:1]
+            self.assertEqual(abspath, b.filename)
+            del b
+            del fp
 
     def test_filename_fileobj(self):
         fp = memmap(self.tmpfp, dtype=self.dtype, mode="w+",
                     shape=self.shape)
         self.assertEqual(fp.filename, self.tmpfp.name)
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index c2ac00923..a6f7b34b9 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -2314,16 +2314,15 @@ def setUp(self):
         shape = (2, 4, 3)
         rand = np.random.random
         self.x = rand(shape) + rand(shape).astype(np.complex)*1j
         self.x[0,:, 1] = [nan, inf, -inf, nan]
         self.dtype = self.x.dtype
-        self.filename = tempfile.mktemp()
+        self.file = tempfile.NamedTemporaryFile()
+        self.filename = self.file.name
 
     def tearDown(self):
-        if os.path.isfile(self.filename):
-            os.unlink(self.filename)
-            #tmp_file.close()
+        self.file.close()
 
     def test_bool_fromstring(self):
         v = np.array([True, False, True, False], dtype=np.bool_)
         y = np.fromstring('1 0 -2.3 0.0', sep=' ', dtype=np.bool_)
         assert_array_equal(v, y)
@@ -2347,11 +2346,10 @@ def test_roundtrip_file(self):
         # NB. doesn't work with flush+seek, due to use of C stdio
         f = open(self.filename, 'rb')
         y = np.fromfile(f, dtype=self.dtype)
         f.close()
         assert_array_equal(y, self.x.flat)
-        os.unlink(self.filename)
 
     def test_roundtrip_filename(self):
         self.x.tofile(self.filename)
         y = np.fromfile(self.filename, dtype=self.dtype)
         assert_array_equal(y, self.x.flat)
@@ -2400,12 +2398,10 @@ def test_file_position_after_fromfile(self):
                 np.fromfile(f, dtype=np.float64, count=1)
                 pos = f.tell()
                 f.close()
                 assert_equal(pos, 10, err_msg=err_msg)
 
-        os.unlink(self.filename)
-
     def test_file_position_after_tofile(self):
         # gh-4118
         sizes = [io.DEFAULT_BUFFER_SIZE//8,
                  io.DEFAULT_BUFFER_SIZE,
                  io.DEFAULT_BUFFER_SIZE*8]
@@ -2429,12 +2425,10 @@ def test_file_position_after_tofile(self):
             np.array([0], dtype=np.float64).tofile(f)
             pos = f.tell()
             f.close()
             assert_equal(pos, 10, err_msg=err_msg)
 
-        os.unlink(self.filename)
-
     def _check_from(self, s, value, **kw):
         y = np.fromstring(asbytes(s), **kw)
         assert_array_equal(y, value)
 
         f = open(self.filename, 'wb')
@@ -2533,11 +2527,10 @@ def test_tofile_sep(self):
         f.close()
         f = open(self.filename, 'r')
         s = f.read()
         f.close()
         assert_equal(s, '1.51,2.0,3.51,4.0')
-        os.unlink(self.filename)
 
     def test_tofile_format(self):
         x = np.array([1.51, 2, 3.51, 4], dtype=float)
         f = open(self.filename, 'w')
         x.tofile(f, sep=',', format='%.2f')
diff --git a/numpy/f2py/__init__.py b/numpy/f2py/__init__.py
index ccdbd4e0b..fcfd1853e 100644
--- a/numpy/f2py/__init__.py
+++ b/numpy/f2py/__init__.py
@@ -26,24 +26,24 @@ def compile(source,
     Read the source of this function for more information.
     '''
     from numpy.distutils.exec_command import exec_command
     import tempfile
     if source_fn is None:
-        fname = os.path.join(tempfile.mktemp()+'.f')
+        f = tempfile.NamedTemporaryFile(suffix='.f')
     else:
-        fname = source_fn
-
-    f = open(fname, 'w')
-    f.write(source)
-    f.close()
-
-    args = ' -c -m %s %s %s'%(modulename, fname, extra_args)
-    c = '%s -c "import numpy.f2py as f2py2e;f2py2e.main()" %s' %(sys.executable, args)
-    s, o = exec_command(c)
-    if source_fn is None:
-        try: os.remove(fname)
-        except OSError: pass
+        f = open(source_fn, 'w')
+
+    try:
+        f.write(source)
+        f.flush()
+
+        args = ' -c -m %s %s %s'%(modulename, f.name, extra_args)
+        c = '%s -c "import numpy.f2py as f2py2e;f2py2e.main()" %s' % \
+                (sys.executable, args)
+        s, o = exec_command(c)
+    finally:
+        f.close()
     return s
 
 from numpy.testing import Tester
 test = Tester().test
 bench = Tester().bench
diff --git a/numpy/f2py/f2py2e.py b/numpy/f2py/f2py2e.py
index ff9d19e9d..25407d421 100755
--- a/numpy/f2py/f2py2e.py
+++ b/numpy/f2py/f2py2e.py
@@ -89,11 +89,11 @@
 
   --[no-]lower     Do [not] lower the cases in <fortran files>. By default,
                    --lower is assumed with -h key, and --no-lower without -h key.
 
   --build-dir <dirname>  All f2py generated files are created in <dirname>.
-                   Default is tempfile.mktemp().
+                   Default is tempfile.mkdtemp().
 
   --overwrite-signature  Overwrite existing signature file.
 
   --[no-]latex-doc Create (or not) <modulename>module.tex.
                    Default is --no-latex-doc.
@@ -422,11 +422,11 @@ def run_compile():
         build_dir = sys.argv[i+1]
         del sys.argv[i+1]
         del sys.argv[i]
     else:
         remove_build_dir = 1
-        build_dir = os.path.join(tempfile.mktemp())
+        build_dir = tempfile.mkdtemp()
 
     _reg1 = re.compile(r'[-][-]link[-]')
     sysinfo_flags = [_m for _m in sys.argv[1:] if _reg1.match(_m)]
     sys.argv = [_m for _m in sys.argv if _m not in sysinfo_flags]
     if sysinfo_flags:
diff --git a/numpy/lib/tests/test_io.py b/numpy/lib/tests/test_io.py
index e3ccb391c..2ee5c838e 100644
--- a/numpy/lib/tests/test_io.py
+++ b/numpy/lib/tests/test_io.py
@@ -2,11 +2,13 @@
 
 import sys
 import gzip
 import os
 import threading
-from tempfile import mkstemp, mktemp, NamedTemporaryFile
+import shutil
+import contextlib
+from tempfile import mkstemp, mkdtemp, NamedTemporaryFile
 import time
 import warnings
 import gc
 from io import BytesIO
 from datetime import datetime
@@ -19,10 +21,16 @@
 from nose import SkipTest
 from numpy.ma.testutils import (TestCase, assert_equal, assert_array_equal,
                                 assert_raises, run_module_suite)
 from numpy.testing import assert_warns, assert_, build_err_msg
 
+@contextlib.contextmanager
+def tempdir(change_dir=False):
+    tmpdir = mkdtemp()
+    yield tmpdir
+    shutil.rmtree(tmpdir)
+
 
 class TextIO(BytesIO):
     """Helper IO class.
 
     Writes encode strings to bytes if needed, reads return bytes.
@@ -175,18 +183,18 @@ def roundtrip(self, *args, **kwargs):
 
     @np.testing.dec.skipif(not IS_64BIT, "Works only with 64bit systems")
     @np.testing.dec.slow
     def test_big_arrays(self):
         L = (1 << 31) + 100000
-        tmp = mktemp(suffix='.npz')
         a = np.empty(L, dtype=np.uint8)
-        np.savez(tmp, a=a)
-        del a
-        npfile = np.load(tmp)
-        a = npfile['a']
-        npfile.close()
-        os.remove(tmp)
+        with tempdir() as tmpdir:
+            tmp = open(os.path.join(tmpdir, "file.npz"), "w")
+            np.savez(tmp, a=a)
+            del a
+            npfile = np.load(tmp)
+            a = npfile['a']
+            npfile.close()
 
     def test_multiple_arrays(self):
         a = np.array([[1, 2], [3, 4]], float)
         b = np.array([[1 + 2j, 2 + 7j], [3 - 6j, 4 + 12j]], complex)
         self.roundtrip(a, b)

Vulnerable code: from tempfile import NamedTemporaryFile, TemporaryFile, mktemp
        tmpname = mktemp('', 'mmap')
        fp = memmap(tmpname, dtype=self.dtype, mode='w+',
                       shape=self.shape)
        fp[:] = self.data[:]
        del fp
        os.unlink(tmpname)
        tmpname = mktemp('', 'mmap')
        fp = memmap(tmpname, dtype=self.dtype, mode='w+',
                       shape=self.shape)
        abspath = os.path.abspath(tmpname)
        fp[:] = self.data[:]
        self.assertEqual(abspath, fp.filename)
        b = fp[:1]
        self.assertEqual(abspath, b.filename)
        del b
        del fp
        os.unlink(tmpname)
        self.filename = tempfile.mktemp()
        if os.path.isfile(self.filename):
            os.unlink(self.filename)
            #tmp_file.close()
        os.unlink(self.filename)
        os.unlink(self.filename)

        os.unlink(self.filename)

        os.unlink(self.filename)
        fname = os.path.join(tempfile.mktemp()+'.f')
        fname = source_fn

    f = open(fname, 'w')
    f.write(source)
    f.close()

    args = ' -c -m %s %s %s'%(modulename, fname, extra_args)
    c = '%s -c "import numpy.f2py as f2py2e;f2py2e.main()" %s' %(sys.executable, args)
    s, o = exec_command(c)
    if source_fn is None:
        try: os.remove(fname)
        except OSError: pass
                   Default is tempfile.mktemp().
        build_dir = os.path.join(tempfile.mktemp())
from tempfile import mkstemp, mktemp, NamedTemporaryFile
        tmp = mktemp(suffix='.npz')
        np.savez(tmp, a=a)
        del a
        npfile = np.load(tmp)
        a = npfile['a']
        npfile.close()
        os.remove(tmp)
Fixed code: from tempfile import NamedTemporaryFile, TemporaryFile
        with NamedTemporaryFile() as tmp:
            fp = memmap(tmp.name, dtype=self.dtype, mode='w+',
                        shape=self.shape)
            fp[:] = self.data[:]
            del fp
        with NamedTemporaryFile() as tmp:
            fp = memmap(tmp.name, dtype=self.dtype, mode='w+',
                        shape=self.shape)
            abspath = os.path.abspath(tmp.name)
            fp[:] = self.data[:]
            self.assertEqual(abspath, fp.filename)
            b = fp[:1]
            self.assertEqual(abspath, b.filename)
            del b
            del fp
        self.file = tempfile.NamedTemporaryFile()
        self.filename = self.file.name
        self.file.close()
        f = tempfile.NamedTemporaryFile(suffix='.f')
        f = open(source_fn, 'w')

    try:
        f.write(source)
        f.flush()

        args = ' -c -m %s %s %s'%(modulename, f.name, extra_args)
        c = '%s -c "import numpy.f2py as f2py2e;f2py2e.main()" %s' % \
                (sys.executable, args)
        s, o = exec_command(c)
    finally:
        f.close()
                   Default is tempfile.mkdtemp().
        build_dir = tempfile.mkdtemp()
import shutil
import contextlib
from tempfile import mkstemp, mkdtemp, NamedTemporaryFile
@contextlib.contextmanager
def tempdir(change_dir=False):
    tmpdir = mkdtemp()
    yield tmpdir
    shutil.rmtree(tmpdir)

        with tempdir() as tmpdir:
            tmp = open(os.path.join(tmpdir, "file.npz"), "w")
            np.savez(tmp, a=a)
            del a
            npfile = np.load(tmp)
            a = npfile['a']
            npfile.close()