diff options
-rw-r--r-- | SHARED-FILES | 5 | ||||
-rw-r--r-- | include/stdio.h | 5 | ||||
-rw-r--r-- | libio/oldtmpfile.c | 1 | ||||
-rw-r--r-- | stdio-common/Makefile | 1 | ||||
-rw-r--r-- | stdio-common/tempnam.c | 1 | ||||
-rw-r--r-- | stdio-common/tempname.c | 12 | ||||
-rw-r--r-- | stdio-common/tmpdir.c | 163 | ||||
-rw-r--r-- | stdio-common/tmpdir.h | 28 | ||||
-rw-r--r-- | stdio-common/tmpfile.c | 1 | ||||
-rw-r--r-- | stdio-common/tmpnam.c | 1 | ||||
-rw-r--r-- | stdio-common/tmpnam_r.c | 1 | ||||
-rw-r--r-- | sysdeps/posix/tempname.c | 173 |
12 files changed, 260 insertions, 132 deletions
diff --git a/SHARED-FILES b/SHARED-FILES index 9c8e715fb5..179130d567 100644 --- a/SHARED-FILES +++ b/SHARED-FILES @@ -111,6 +111,9 @@ gnulib: string/strstr.c string/strtok_r.c string/strverscmp.c + # Merged from gnulib 2024-04-08 (gnulib commit 3238349628) + stdio-common/tmpdir.c + stdio-common/tmpdir.h sysdeps/generic/pty-private.h sysdeps/generic/siglist.h sysdeps/posix/euidaccess.c @@ -118,7 +121,7 @@ gnulib: sysdeps/posix/getcwd.c sysdeps/posix/pwrite.c sysdeps/posix/spawni.c - # Merged from gnulib 2014-6-23 + # Merged from gnulib 2024-04-08 (gnulib commit 3238349628) sysdeps/posix/tempname.c # Merged from gnulib 2014-6-27 time/mktime.c diff --git a/include/stdio.h b/include/stdio.h index 24f1652f19..e48d709919 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -156,11 +156,6 @@ extern FILE *__old_tmpfile (void); # include <bits/types/wint_t.h> -/* Generate a unique file name (and possibly open it). */ -extern int __path_search (char *__tmpl, size_t __tmpl_len, - const char *__dir, const char *__pfx, - int __try_tempdir) attribute_hidden; - extern int __gen_tempname (char *__tmpl, int __suffixlen, int __flags, int __kind) attribute_hidden; /* The __kind argument to __gen_tempname may be one of: */ diff --git a/libio/oldtmpfile.c b/libio/oldtmpfile.c index af467a6e5a..f09ee0600c 100644 --- a/libio/oldtmpfile.c +++ b/libio/oldtmpfile.c @@ -22,6 +22,7 @@ #include <stdio.h> #include <unistd.h> #include <iolibio.h> +#include <stdio-common/tmpdir.h> /* This returns a new stream opened on a temporary file (generated by tmpnam). The file is opened with mode "w+b" (binary read/write). diff --git a/stdio-common/Makefile b/stdio-common/Makefile index 6447b6b444..6bc972af1a 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -92,6 +92,7 @@ routines := \ sscanf \ tempnam \ tempname \ + tmpdir \ tmpfile \ tmpfile64 \ tmpnam \ diff --git a/stdio-common/tempnam.c b/stdio-common/tempnam.c index 0f8eaf3535..9e62442451 100644 --- a/stdio-common/tempnam.c +++ b/stdio-common/tempnam.c @@ -17,6 +17,7 @@ #include <stdio.h> #include <string.h> +#include "tmpdir.h" /* Generate a unique temporary filename using up to five characters of PFX if it is not NULL. The directory to put this file in is diff --git a/stdio-common/tempname.c b/stdio-common/tempname.c index d88f1c3946..77e695ca5f 100644 --- a/stdio-common/tempname.c +++ b/stdio-common/tempname.c @@ -20,18 +20,6 @@ #include <stdio.h> #include <errno.h> -/* Perform the "SVID path search malarkey" on DIR and PFX. Write a - template suitable for use in __gen_tempname into TMPL, bounded - by TMPL_LEN. */ -int -__path_search (char *tmpl, size_t tmpl_len, const char *dir, const char *pfx, - int try_tmpdir) -{ - __set_errno (ENOSYS); - return -1; -} -stub_warning (__path_search) - /* Generate a (hopefully) unique temporary filename in DIR (if applicable), using template TMPL. KIND determines what to do with that name. It may be one of: diff --git a/stdio-common/tmpdir.c b/stdio-common/tmpdir.c new file mode 100644 index 0000000000..f189e85778 --- /dev/null +++ b/stdio-common/tmpdir.c @@ -0,0 +1,163 @@ +/* Copyright (C) 1999, 2001-2002, 2006, 2009-2024 Free Software Foundation, + Inc. + This file is part of the GNU C Library. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Extracted from sysdeps/posix/tempname.c. */ + +#include <config.h> + +/* Specification. */ +#include "tmpdir.h" + +#include <stdlib.h> +#include <string.h> + +#include <errno.h> +#ifndef __set_errno +# define __set_errno(Val) errno = (Val) +#endif + +#include <stdio.h> +#ifndef P_tmpdir +# ifdef _P_tmpdir /* native Windows */ +# define P_tmpdir _P_tmpdir +# else +# define P_tmpdir "/tmp" +# endif +#endif + +#include <sys/stat.h> + +#if defined _WIN32 && ! defined __CYGWIN__ +# define WIN32_LEAN_AND_MEAN /* avoid including junk */ +# include <windows.h> +#endif + +#if defined _WIN32 && ! defined __CYGWIN__ +/* Don't assume that UNICODE is not defined. */ +# undef GetTempPath +# define GetTempPath GetTempPathA +#endif + +#if _LIBC +# define struct_stat64 struct __stat64_t64 +#else +# include "pathmax.h" +# define struct_stat64 struct stat +# define __libc_secure_getenv secure_getenv +# define __stat64_time64(path, buf) stat (path, buf) +#endif + +/* Pathname support. + ISSLASH(C) tests whether C is a directory separator character. + */ +#if defined _WIN32 || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__ + /* Native Windows, Cygwin, OS/2, DOS */ +# define ISSLASH(C) ((C) == '/' || (C) == '\\') +#else + /* Unix */ +# define ISSLASH(C) ((C) == '/') +#endif + + +/* Return nonzero if DIR is an existent directory. */ +static bool +direxists (const char *dir) +{ + struct_stat64 buf; + return __stat64_time64 (dir, &buf) == 0 && S_ISDIR (buf.st_mode); +} + +/* Path search algorithm, for tmpnam, tmpfile, etc. If DIR is + non-null and exists, uses it; otherwise uses the first of $TMPDIR, + P_tmpdir, /tmp that exists. Copies into TMPL a template suitable + for use with mk[s]temp. Will fail (-1) if DIR is non-null and + doesn't exist, none of the searched dirs exists, or there's not + enough space in TMPL. */ +int +__path_search (char *tmpl, size_t tmpl_len, const char *dir, const char *pfx, + bool try_tmpdir) +{ + const char *d; + size_t dlen, plen; + bool add_slash; + + if (!pfx || !pfx[0]) + { + pfx = "file"; + plen = 4; + } + else + { + plen = strlen (pfx); + if (plen > 5) + plen = 5; + } + + if (try_tmpdir) + { + d = __libc_secure_getenv ("TMPDIR"); + if (d != NULL && direxists (d)) + dir = d; + else if (dir != NULL && direxists (dir)) + /* nothing */ ; + else + dir = NULL; + } + if (dir == NULL) + { +#if defined _WIN32 && ! defined __CYGWIN__ + char dirbuf[PATH_MAX]; + DWORD retval; + + /* Find Windows temporary file directory. + We try this before P_tmpdir because Windows defines P_tmpdir to "\\" + and will therefore try to put all temporary files in the root + directory (unless $TMPDIR is set). */ + retval = GetTempPath (PATH_MAX, dirbuf); + if (retval > 0 && retval < PATH_MAX && direxists (dirbuf)) + dir = dirbuf; + else +#endif + if (direxists (P_tmpdir)) + dir = P_tmpdir; + else if (strcmp (P_tmpdir, "/tmp") != 0 && direxists ("/tmp")) + dir = "/tmp"; + else + { + __set_errno (ENOENT); + return -1; + } + } + + dlen = strlen (dir); +#ifdef __VMS + add_slash = 0; +#else + add_slash = dlen != 0 && !ISSLASH (dir[dlen - 1]); +#endif + + /* check we have room for "${dir}/${pfx}XXXXXX\0" */ + if (tmpl_len < dlen + add_slash + plen + 6 + 1) + { + __set_errno (EINVAL); + return -1; + } + + memcpy (tmpl, dir, dlen); + sprintf (tmpl + dlen, &"/%.*sXXXXXX"[!add_slash], (int) plen, pfx); + return 0; +} diff --git a/stdio-common/tmpdir.h b/stdio-common/tmpdir.h new file mode 100644 index 0000000000..e187a31d65 --- /dev/null +++ b/stdio-common/tmpdir.h @@ -0,0 +1,28 @@ +/* Determine a temporary directory. + Copyright (C) 2001-2002, 2009-2024 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +#include <stdbool.h> +#include <stddef.h> + +/* Path search algorithm, for tmpnam, tmpfile, etc. If DIR is + non-null and exists, uses it; otherwise uses the first of $TMPDIR, + P_tmpdir, /tmp that exists. Copies into TMPL a template suitable + for use with mk[s]temp. Will fail (-1) if DIR is non-null and + doesn't exist, none of the searched dirs exists, or there's not + enough space in TMPL. */ +extern int __path_search (char *tmpl, size_t tmpl_len, const char *dir, + const char *pfx, bool try_tmpdir) + attribute_hidden; diff --git a/stdio-common/tmpfile.c b/stdio-common/tmpfile.c index 9c49483fca..08cf9284bb 100644 --- a/stdio-common/tmpfile.c +++ b/stdio-common/tmpfile.c @@ -19,6 +19,7 @@ #include <fcntl.h> #include <stdio.h> #include <unistd.h> +#include "tmpdir.h" #include <iolibio.h> #define __fdopen _IO_fdopen diff --git a/stdio-common/tmpnam.c b/stdio-common/tmpnam.c index b02ad952e9..1cff363718 100644 --- a/stdio-common/tmpnam.c +++ b/stdio-common/tmpnam.c @@ -17,6 +17,7 @@ #include <stdio.h> #include <string.h> +#include "tmpdir.h" static char tmpnam_buffer[L_tmpnam]; diff --git a/stdio-common/tmpnam_r.c b/stdio-common/tmpnam_r.c index 2a3598af36..38f9bace9b 100644 --- a/stdio-common/tmpnam_r.c +++ b/stdio-common/tmpnam_r.c @@ -16,6 +16,7 @@ <https://www.gnu.org/licenses/>. */ #include <stdio.h> +#include "tmpdir.h" /* Generate a unique filename in P_tmpdir. If S is NULL return NULL. This makes this function thread safe. */ diff --git a/sysdeps/posix/tempname.c b/sysdeps/posix/tempname.c index 2af22386eb..c00fe0c181 100644 --- a/sysdeps/posix/tempname.c +++ b/sysdeps/posix/tempname.c @@ -20,16 +20,9 @@ # include "tempname.h" #endif -#include <sys/types.h> -#include <assert.h> -#include <stdbool.h> - #include <errno.h> #include <stdio.h> -#ifndef P_tmpdir -# define P_tmpdir "/tmp" -#endif #ifndef TMP_MAX # define TMP_MAX 238328 #endif @@ -43,12 +36,10 @@ # error report this to bug-gnulib@gnu.org #endif -#include <stddef.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> -#include <stdalign.h> #include <stdint.h> #include <sys/random.h> #include <sys/stat.h> @@ -56,14 +47,12 @@ #if _LIBC # define struct_stat64 struct __stat64_t64 -# define __secure_getenv __libc_secure_getenv #else # define struct_stat64 struct stat # define __gen_tempname gen_tempname # define __mkdir mkdir # define __open open # define __lstat64_time64(file, buf) lstat (file, buf) -# define __stat64(file, buf) stat (file, buf) # define __getrandom getrandom # define __clock_gettime64 clock_gettime # define __timespec64 timespec @@ -77,94 +66,60 @@ typedef uint_fast64_t random_value; #define BASE_62_DIGITS 10 /* 62**10 < UINT_FAST64_MAX */ #define BASE_62_POWER (62LL * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62) +/* Return the result of mixing the entropy from R and S. + Assume that R and S are not particularly random, + and that the result should look randomish to an untrained eye. */ + static random_value -random_bits (random_value var, bool use_getrandom) +mix_random_values (random_value r, random_value s) { - random_value r; - /* Without GRND_NONBLOCK it can be blocked for minutes on some systems. */ - if (use_getrandom && __getrandom (&r, sizeof r, GRND_NONBLOCK) == sizeof r) - return r; -#if _LIBC || (defined CLOCK_MONOTONIC && HAVE_CLOCK_GETTIME) - /* Add entropy if getrandom did not work. */ - struct __timespec64 tv; - __clock_gettime64 (CLOCK_MONOTONIC, &tv); - var ^= tv.tv_nsec; -#endif - return 2862933555777941757 * var + 3037000493; + /* As this code is used only when high-quality randomness is neither + available nor necessary, there is no need for fancier polynomials + such as those in the Linux kernel's 'random' driver. */ + return (2862933555777941757 * r + 3037000493) ^ s; } -#if _LIBC -/* Return nonzero if DIR is an existent directory. */ -static int -direxists (const char *dir) -{ - struct_stat64 buf; - return __stat64_time64 (dir, &buf) == 0 && S_ISDIR (buf.st_mode); -} +/* Set *R to a random value. + Return true if *R is set to high-quality value taken from getrandom. + Otherwise return false, falling back to a low-quality *R that might + depend on S. -/* Path search algorithm, for tmpnam, tmpfile, etc. If DIR is - non-null and exists, uses it; otherwise uses the first of $TMPDIR, - P_tmpdir, /tmp that exists. Copies into TMPL a template suitable - for use with mk[s]temp. Will fail (-1) if DIR is non-null and - doesn't exist, none of the searched dirs exists, or there's not - enough space in TMPL. */ -int -__path_search (char *tmpl, size_t tmpl_len, const char *dir, const char *pfx, - int try_tmpdir) + This function returns false only when getrandom fails. + On GNU systems this should happen only early in the boot process, + when the fallback should be good enough for programs using tempname + because any attacker likely has root privileges already. */ + +static bool +random_bits (random_value *r, random_value s) { - const char *d; - size_t dlen, plen; + /* Without GRND_NONBLOCK it can be blocked for minutes on some systems. */ + if (__getrandom (r, sizeof *r, GRND_NONBLOCK) == sizeof *r) + return true; - if (!pfx || !pfx[0]) - { - pfx = "file"; - plen = 4; - } - else - { - plen = strlen (pfx); - if (plen > 5) - plen = 5; - } + /* If getrandom did not work, use ersatz entropy based on low-order + clock bits. On GNU systems getrandom should fail only + early in booting, when ersatz should be good enough. + Do not use ASLR-based entropy, as that would leak ASLR info into + the resulting file name which is typically public. - if (try_tmpdir) - { - d = __secure_getenv ("TMPDIR"); - if (d != NULL && direxists (d)) - dir = d; - else if (dir != NULL && direxists (dir)) - /* nothing */ ; - else - dir = NULL; - } - if (dir == NULL) - { - if (direxists (P_tmpdir)) - dir = P_tmpdir; - else if (strcmp (P_tmpdir, "/tmp") != 0 && direxists ("/tmp")) - dir = "/tmp"; - else - { - __set_errno (ENOENT); - return -1; - } - } + Of course we are in a state of sin here. */ - dlen = strlen (dir); - while (dlen > 1 && dir[dlen - 1] == '/') - dlen--; /* remove trailing slashes */ + random_value v = s; - /* check we have room for "${dir}/${pfx}XXXXXX\0" */ - if (tmpl_len < dlen + 1 + plen + 6 + 1) - { - __set_errno (EINVAL); - return -1; - } +#if _LIBC || (defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME) + struct __timespec64 tv; + __clock_gettime64 (CLOCK_REALTIME, &tv); + v = mix_random_values (v, tv.tv_sec); + v = mix_random_values (v, tv.tv_nsec); +#endif - sprintf (tmpl, "%.*s/%.*sXXXXXX", (int) dlen, dir, (int) plen, pfx); - return 0; + /* In glibc, clock_gettime with CLOCK_REALTIME is expected to always + succeed. */ +#if !_LIBC + *r = mix_random_values (v, clock ()); +#endif + return false; } -#endif /* _LIBC */ #if _LIBC static int try_tempname_len (char *, int, void *, int (*) (char *, void *), @@ -213,7 +168,7 @@ static const char letters[] = and return a read-write fd. The file is mode 0600. __GT_DIR: create a directory, which will be mode 0700. - We use a clever algorithm to get hard-to-predict names. */ + */ #ifdef _LIBC static #endif @@ -242,7 +197,7 @@ try_tempname_len (char *tmpl, int suffixlen, void *args, char *XXXXXX; unsigned int count; int fd = -1; - int save_errno = errno; + int saved_errno = errno; /* A lower bound on the number of temporary files to attempt to generate. The maximum total number of temporary file names that @@ -261,25 +216,17 @@ try_tempname_len (char *tmpl, int suffixlen, void *args, unsigned int attempts = ATTEMPTS_MIN; #endif - /* A random variable. The initial value is used only the for fallback path - on 'random_bits' on 'getrandom' failure. Its initial value tries to use - some entropy from the ASLR and ignore possible bits from the stack - alignment. */ - random_value v = ((uintptr_t) &v) / alignof (max_align_t); + /* A random variable. */ + random_value v = 0; - /* How many random base-62 digits can currently be extracted from V. */ + /* A value derived from the random variable, and how many random + base-62 digits can currently be extracted from VDIGBUF. */ + random_value vdigbuf; int vdigits = 0; - /* Whether to consume entropy when acquiring random bits. On the - first try it's worth the entropy cost with __GT_NOCREATE, which - is inherently insecure and can use the entropy to make it a bit - less secure. On the (rare) second and later attempts it might - help against DoS attacks. */ - bool use_getrandom = tryfunc == try_nocreate; - - /* Least unfair value for V. If V is less than this, V can generate - BASE_62_DIGITS digits fairly. Otherwise it might be biased. */ - random_value const unfair_min + /* Least biased value for V. If V is less than this, V can generate + BASE_62_DIGITS unbiased digits. Otherwise the digits are biased. */ + random_value const biased_min = RANDOM_VALUE_MAX - RANDOM_VALUE_MAX % BASE_62_POWER; len = strlen (tmpl); @@ -299,25 +246,23 @@ try_tempname_len (char *tmpl, int suffixlen, void *args, { if (vdigits == 0) { - do - { - v = random_bits (v, use_getrandom); - use_getrandom = true; - } - while (unfair_min <= v); + /* Worry about bias only if the bits are high quality. */ + while (random_bits (&v, v) && biased_min <= v) + continue; + vdigbuf = v; vdigits = BASE_62_DIGITS; } - XXXXXX[i] = letters[v % 62]; - v /= 62; + XXXXXX[i] = letters[vdigbuf % 62]; + vdigbuf /= 62; vdigits--; } fd = tryfunc (tmpl, args); if (fd >= 0) { - __set_errno (save_errno); + __set_errno (saved_errno); return fd; } else if (errno != EEXIST) |