git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2 0/2] Generate temporary files using a CSPRNG
@ 2022-01-04  1:55 brian m. carlson
  2022-01-04  1:55 ` [PATCH v2 1/2] wrapper: add a helper to generate numbers from " brian m. carlson
                   ` (3 more replies)
  0 siblings, 4 replies; 23+ messages in thread
From: brian m. carlson @ 2022-01-04  1:55 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, rsbecker, Taylor Blau,
	Carlo Marcelo Arenas Belón

Currently, when we generate a temporary file name, we use the seconds,
microseconds, and the PID to generate a unique value.  The resulting
value, while changing frequently, is actually predictable and on some
systems, it may be possible to cause a DoS by creating all potential
temporary files when the temporary file is being created in TMPDIR.

The solution to this is to use the system CSPRNG to generate the
temporary file name.  This is the approach taken by FreeBSD, NetBSD, and
OpenBSD, and glibc also recently switched to this approach from an
approach that resembled ours in many ways.

Even if this is not practically exploitable on many systems, it seems
prudent to be at least as careful about temporary file generation as
libc is.

In this round, I've switched to using a single Makefile variable instead
of multiple.  This lets folks set the compile options in a simpler way,
but it does require that we strip whitespace when parsing the values,
since we have several places in our makefiles where we add extra
whitespace.  We still have multiple #defines because that's functionally
required.

I've also added support for the OpenSSL CSPRNG here, which should
address concerns for NonStop.  Because OpenSSL is very portable across
systems and a TLS library is effectively required for a generally useful
Git on the Internet today, we can assume that this is a safe fallback.

I have noted in my updated commit message that POSIX is set to
standardize getentropy in a future revision of the standard.  Of course,
we don't demand that systems implement a specific version of POSIX, but
that does mean that this code will likely shrink somewhat over time
instead of increasing in size.  That's my hope, at least.

I have, in light of the above additions, chosen not to add a fallback to
an insecure option.  I think we provide plenty of options that should
meet the needs of all users and in the unlikely event we find a system
which simply cannot provide the relevant interface, we can add one then.

There was a proposal to switch to using mkstemp instead.  I have not
adopted that approach because it is strictly less functional than our
current approach (e.g., it would not support modes properly), I have
doubts about its portability (in which case we'd have to reimplement it
using something like this series anyway), and as mentioned in the
thread, I plan to investigate further uses of this code in the future.

For those that are interested in the nitty-gritty details after reading
the commit message, I did end up determining that modern macOS supports
the arc4random interfaces we require.  It currently calls out to its own
CTR DRBG in CoreCrypto, which is why it doesn't use ChaCha.  MirBSD also
supports these interfaces, but still uses RC4.  We use those interfaces
anyway and will let them deal with the consequences, since the interface
is stated to use a CSPRNG and as far as I can tell the kernel uses the
same algorithm.  All the other BSDs I checked (and libbsd) use ChaCha
and have the boring and secure OpenBSD-like behavior.

A range-diff appears below for your convenience.

Changes from v1:
* Remove the automatic testing using buckets because it didn't seem to
  add much.
* Switch to a single makefile variable.
* Add support for OpenSSL CSPRNG.
* Add more defaults for various systems, including macOS and NonStop.
* Add an arc4random-libbsd variant for improved testing of the
  arc4random code paths on Linux.  The only difference is the inclusion
  of an additional header.
* Print a more useful error message than before, indicating that the
  CSPRNG failed.

brian m. carlson (2):
  wrapper: add a helper to generate numbers from a CSPRNG
  wrapper: use a CSPRNG to generate random file names

 Makefile                            | 33 ++++++++++++
 compat/winansi.c                    |  6 +++
 config.mak.uname                    |  8 +++
 contrib/buildsystems/CMakeLists.txt |  2 +-
 git-compat-util.h                   | 19 +++++++
 t/helper/test-csprng.c              | 29 +++++++++++
 t/helper/test-tool.c                |  1 +
 t/helper/test-tool.h                |  1 +
 wrapper.c                           | 81 +++++++++++++++++++++++++----
 9 files changed, 168 insertions(+), 12 deletions(-)
 create mode 100644 t/helper/test-csprng.c

Diff-intervalle contre v1 :
1:  8ffb65a1f8 ! 1:  edd623bd9a wrapper: add a helper to generate numbers from a CSPRNG
    @@ Commit message
         To make this possible, let's add a function which reads from a system
         CSPRNG and returns some bytes.
     
    +    We know that all systems will have such an interface.  A CSPRNG is
    +    required for a secure TLS or SSH implementation and a Git implementation
    +    which provided neither would be of little practical use.  In addition,
    +    POSIX is set to standardize getentropy(2) in the next version, so in the
    +    (potentially distant) future we can rely on that.
    +
    +    For systems which lack one of the other interfaces, we provide the
    +    ability to use OpenSSL's CSPRNG.  OpenSSL is highly portable and
    +    functions on practically every known OS, and we know it will have access
    +    to some source of cryptographically secure randomness.  We also provide
    +    support for the arc4random in libbsd for folks who would prefer to use
    +    that.
    +
         Because this is a security sensitive interface, we take some
         precautions.  We either succeed by filling the buffer completely as we
         requested, or we fail.  We don't return partial data because the caller
         will almost never find that to be a useful behavior.
     
    -    The order of options is also important here.  On systems with
    -    arc4random, which is most of the BSDs, we use that, since, except on
    -    MirBSD, it uses ChaCha20, which is extremely fast, and sits entirely in
    -    userspace, avoiding a system call.  We then prefer getrandom over
    -    getentropy, because the former has been available longer on Linux, and
    -    finally, if none of those are available, we use /dev/urandom, because
    -    most Unix-like operating systems provide that API.  We prefer options
    -    that don't involve device files when possible because those work in some
    -    restricted environments where device files may not be available.
    +    Specify a makefile knob which users can use to specify their preferred
    +    CSPRNG, and turn the multiple string options into a set of defines,
    +    since we cannot match on strings in the preprocessor.
     
    -    macOS appears to have arc4random but not the arc4random_buf function we
    -    want to use, so we let it use the fallback of /dev/urandom.  Set the
    -    configuration variables appropriately for Linux and the other BSDs.  We
    -    specifically only consider versions which receive publicly available
    -    security support; for example, getrandom(2) and getentropy(3) are only
    -    available in FreeBSD 12, which is the oldest version with current
    -    security support.  For the same reason, we don't specify getrandom(2) on
    -    Linux, because CentOS 7 doesn't support it in glibc (although its kernel
    -    does) and we don't want to resort to making syscalls.
    +    The order of suggested options is important here.  On systems with
    +    arc4random, which is most of the BSDs, we suggest that, since, except on
    +    MirBSD and macOS, it uses ChaCha20, which is extremely fast, and sits
    +    entirely in userspace, avoiding a system call.  We then prefer getrandom
    +    over getentropy, because the former has been available longer on Linux,
    +    and then OpenSSL. Finally, if none of those are available, we use
    +    /dev/urandom, because most Unix-like operating systems provide that API.
    +    We prefer to suggest options that don't involve device files when
    +    possible because those work in some restricted environments where device
    +    files may not be available.
     
    -    Finally, add a self-test option here to make sure that our buffer
    -    handling is correct and we aren't truncating data.  We simply read 64
    -    KiB and then make sure we've seen each byte.  The probability of this
    -    test failing spuriously is less than 10^-100.
    +    Set the configuration variables appropriately for Linux and the BSDs,
    +    including macOS, as well as Windows and NonStop.  We specifically only
    +    consider versions which receive publicly available security support
    +    here.  For the same reason, we don't specify getrandom(2) on Linux,
    +    because CentOS 7 doesn't support it in glibc (although its kernel does)
    +    and we don't want to resort to making syscalls.
    +
    +    Finally, add a test helper to allow this to be tested by hand and in
    +    tests.  We don't add any tests, since invoking the CSPRNG is not likely
    +    to produce interesting, reproducible results.
     
         Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
     
    @@ Makefile: all::
      # Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support
      # the executable mode bit, but doesn't really do so.
      #
    -+# Define HAVE_ARC4RANDOM if your system has arc4random and arc4random_buf.
    -+#
    -+# Define HAVE_GETRANDOM if your system has getrandom.
    -+#
    -+# Define HAVE_GETENTROPY if your system has getentropy.
    -+#
    -+# Define HAVE_RTLGENRANDOM if your system has RtlGenRandom (Windows only).
    ++# Define CSPRNG_METHOD to "arc4random" if your system has arc4random and
    ++# arc4random_buf, "arc4random-libbsd" if your system has those functions from
    ++# libbsd, "getrandom" if your system has getrandom, "getentropy" if your
    ++# system has getentropy, "rtlgenrandom" for RtlGenRandom (Windows only), or
    ++# "openssl" if you'd want to use the OpenSSL CSPRNG.  If unset or set to
    ++# anything else, defaults to using "/dev/urandom".
     +#
      # Define NEEDS_MODE_TRANSLATION if your OS strays from the typical file type
      # bits in mode values (e.g. z/OS defines I_SFMT to 0xFF000000 as opposed to the
    @@ Makefile: ifdef HAVE_GETDELIM
      	BASIC_CFLAGS += -DHAVE_GETDELIM
      endif
      
    -+ifdef HAVE_ARC4RANDOM
    ++ifeq ($(strip $(CSPRNG_METHOD)),arc4random)
     +	BASIC_CFLAGS += -DHAVE_ARC4RANDOM
     +endif
     +
    -+ifdef HAVE_GETRANDOM
    ++ifeq ($(strip $(CSPRNG_METHOD)),arc4random-libbsd)
    ++	BASIC_CFLAGS += -DHAVE_ARC4RANDOM_LIBBSD
    ++	EXTLIBS += -lbsd
    ++endif
    ++
    ++ifeq ($(strip $(CSPRNG_METHOD)),getrandom)
     +	BASIC_CFLAGS += -DHAVE_GETRANDOM
     +endif
     +
    -+ifdef HAVE_GETENTROPY
    ++ifeq ($(strip $(CSPRNG_METHOD)),getentropy)
     +	BASIC_CFLAGS += -DHAVE_GETENTROPY
     +endif
     +
    -+ifdef HAVE_RTLGENRANDOM
    ++ifeq ($(strip $(CSPRNG_METHOD)),rtlgenrandom)
     +	BASIC_CFLAGS += -DHAVE_RTLGENRANDOM
     +endif
    ++
    ++ifeq ($(strip $(CSPRNG_METHOD)),openssl)
    ++	BASIC_CFLAGS += -DHAVE_OPENSSL_CSPRNG
    ++endif
     +
      ifneq ($(PROCFS_EXECUTABLE_PATH),)
      	procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH))
    @@ compat/winansi.c
      #include <winreg.h>
     
      ## config.mak.uname ##
    +@@ config.mak.uname: ifeq ($(uname_S),Darwin)
    + 	HAVE_BSD_SYSCTL = YesPlease
    + 	FREAD_READS_DIRECTORIES = UnfortunatelyYes
    + 	HAVE_NS_GET_EXECUTABLE_PATH = YesPlease
    ++	CSPRNG_METHOD = arc4random
    + 
    + 	# Workaround for `gettext` being keg-only and not even being linked via
    + 	# `brew link --force gettext`, should be obsolete as of
     @@ config.mak.uname: ifeq ($(uname_S),FreeBSD)
      	HAVE_PATHS_H = YesPlease
      	HAVE_BSD_SYSCTL = YesPlease
      	HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
    -+	HAVE_ARC4RANDOM = YesPlease
    -+	HAVE_GETRANDOM = YesPlease
    -+	HAVE_GETENTROPY = YesPlease
    ++	CSPRNG_METHOD = arc4random
      	PAGER_ENV = LESS=FRX LV=-c MORE=FRX
      	FREAD_READS_DIRECTORIES = UnfortunatelyYes
      	FILENO_IS_A_MACRO = UnfortunatelyYes
    @@ config.mak.uname: ifeq ($(uname_S),OpenBSD)
      	HAVE_PATHS_H = YesPlease
      	HAVE_BSD_SYSCTL = YesPlease
      	HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
    -+	HAVE_ARC4RANDOM = YesPlease
    -+	HAVE_GETENTROPY = YesPlease
    ++	CSPRNG_METHOD = arc4random
      	PROCFS_EXECUTABLE_PATH = /proc/curproc/file
      	FREAD_READS_DIRECTORIES = UnfortunatelyYes
      	FILENO_IS_A_MACRO = UnfortunatelyYes
    @@ config.mak.uname: ifeq ($(uname_S),MirBSD)
      	NEEDS_LIBICONV = YesPlease
      	HAVE_PATHS_H = YesPlease
      	HAVE_BSD_SYSCTL = YesPlease
    -+	HAVE_ARC4RANDOM = YesPlease
    ++	CSPRNG_METHOD = arc4random
      endif
      ifeq ($(uname_S),NetBSD)
      	ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
    @@ config.mak.uname: ifeq ($(uname_S),NetBSD)
      	HAVE_PATHS_H = YesPlease
      	HAVE_BSD_SYSCTL = YesPlease
      	HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
    -+	HAVE_ARC4RANDOM = YesPlease
    ++	CSPRNG_METHOD = arc4random
      	PROCFS_EXECUTABLE_PATH = /proc/curproc/exe
      endif
      ifeq ($(uname_S),AIX)
    @@ config.mak.uname: ifeq ($(uname_S),Windows)
      	NO_STRTOUMAX = YesPlease
      	NO_MKDTEMP = YesPlease
      	NO_INTTYPES_H = YesPlease
    -+	HAVE_RTLGENRANDOM = YesPlease
    ++	CSPRNG_METHOD = rtlgenrandom
      	# VS2015 with UCRT claims that snprintf and friends are C99 compliant,
      	# so we don't need this:
      	#
    +@@ config.mak.uname: ifeq ($(uname_S),NONSTOP_KERNEL)
    + 	NO_MMAP = YesPlease
    + 	NO_POLL = YesPlease
    + 	NO_INTPTR_T = UnfortunatelyYes
    ++	CSPRNG_METHOD = openssl
    + 	SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
    + 	SHELL_PATH = /usr/coreutils/bin/bash
    + endif
     @@ config.mak.uname: ifeq ($(uname_S),MINGW)
      	NO_POSIX_GOODIES = UnfortunatelyYes
      	DEFAULT_HELP_FORMAT = html
      	HAVE_PLATFORM_PROCINFO = YesPlease
    -+	HAVE_RTLGENRANDOM = YesPlease
    ++	CSPRNG_METHOD = rtlgenrandom
      	BASIC_LDFLAGS += -municode
      	COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
      	COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
    @@ git-compat-util.h
      #else
      #include <stdint.h>
      #endif
    ++#ifdef HAVE_ARC4RANDOM_LIBBSD
    ++#include <bsd/stdlib.h>
    ++#endif
     +#ifdef HAVE_GETRANDOM
     +#include <sys/random.h>
     +#endif
    @@ t/helper/test-csprng.c (new)
     +#include "test-tool.h"
     +#include "git-compat-util.h"
     +
    -+/*
    -+ * Check that we read each byte value at least once when reading 64 KiB from the
    -+ * CSPRNG.  This is not to test the quality of the CSPRNG, but to test our
    -+ * buffer handling of it.
    -+ *
    -+ * The probability of this failing by random is less than 10^-100.
    -+ */
    -+static int selftest(void)
    -+{
    -+	int buckets[256] = { 0 };
    -+	unsigned char buf[1024];
    -+	unsigned long count = 64 * 1024;
    -+	int i;
    -+
    -+	while (count) {
    -+		if (csprng_bytes(buf, sizeof(buf)) < 0) {
    -+			perror("failed to read");
    -+			return 3;
    -+		}
    -+		for (i = 0; i < sizeof(buf); i++)
    -+			buckets[buf[i]]++;
    -+		count -= sizeof(buf);
    -+	}
    -+	for (i = 0; i < ARRAY_SIZE(buckets); i++)
    -+		if (!buckets[i]) {
    -+			fprintf(stderr, "failed to find any bytes with value %02x\n", i);
    -+			return 4;
    -+		}
    -+	return 0;
    -+}
     +
     +int cmd__csprng(int argc, const char **argv)
     +{
    @@ t/helper/test-csprng.c (new)
     +	unsigned char buf[1024];
     +
     +	if (argc > 2) {
    -+		fprintf(stderr, "usage: %s [--selftest | <size>]\n", argv[0]);
    ++		fprintf(stderr, "usage: %s [<size>]\n", argv[0]);
     +		return 2;
     +	}
     +
    -+	if (!strcmp(argv[1], "--selftest")) {
    -+		return selftest();
    -+	}
    -+
     +	count = (argc == 2) ? strtoul(argv[1], NULL, 0) : -1L;
     +
     +	while (count) {
    @@ t/helper/test-tool.h: int cmd__bloom(int argc, const char **argv);
      int cmd__date(int argc, const char **argv);
      int cmd__delta(int argc, const char **argv);
     
    - ## t/t0000-basic.sh ##
    -@@ t/t0000-basic.sh: test_expect_success 'test_must_fail rejects a non-git command with env' '
    - 	grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
    - '
    - 
    -+test_expect_success 'CSPRNG handling functions correctly' '
    -+	test-tool csprng --selftest
    -+'
    -+
    - test_done
    -
      ## wrapper.c ##
     @@ wrapper.c: int open_nofollow(const char *path, int flags)
      	return open(path, flags);
    @@ wrapper.c: int open_nofollow(const char *path, int flags)
     +
     +int csprng_bytes(void *buf, size_t len)
     +{
    -+#if defined(HAVE_ARC4RANDOM)
    ++#if defined(HAVE_ARC4RANDOM) || defined(HAVE_ARC4RANDOM_LIBBSD)
    ++	/* This function never returns an error. */
     +	arc4random_buf(buf, len);
     +	return 0;
     +#elif defined(HAVE_GETRANDOM)
    @@ wrapper.c: int open_nofollow(const char *path, int flags)
     +	if (!RtlGenRandom(buf, len))
     +		return -1;
     +	return 0;
    ++#elif defined(HAVE_OPENSSL_CSPRNG)
    ++	int res = RAND_bytes(buf, len);
    ++	if (res == 1)
    ++		return 0;
    ++	if (res == -1)
    ++		errno = ENOTSUP;
    ++	else
    ++		errno = EIO;
    ++	return -1;
     +#else
     +	ssize_t res;
     +	char *p = buf;
2:  f7212e0dce ! 2:  b4cd8700e3 wrapper: use a CSPRNG to generate random file names
    @@ Commit message
         Unfortunately, this is not the best idea from a security perspective.
         If we're writing into TMPDIR, an attacker can guess these values easily
         and prevent us from creating any temporary files at all by creating them
    -    all first.  POSIX only requires TMP_MAX to be 25, so this is achievable
    +    all first.  Even though we set TMP_MAX to 16384, this may be achievable
         in some contexts, even if unlikely to occur in practice.
     
         Fortunately, we can simply solve this by using the system
    @@ wrapper.c: int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
      		int i;
     +		uint64_t v;
     +		if (csprng_bytes(&v, sizeof(v)) < 0)
    -+			return -1;
    ++			return error_errno("unable to get random bytes for temporary file");
     +
      		/* Fill in the random bits. */
      		for (i = 0; i < num_x; i++) {

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2022-01-24 17:08 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-01-04  1:55 [PATCH v2 0/2] Generate temporary files using a CSPRNG brian m. carlson
2022-01-04  1:55 ` [PATCH v2 1/2] wrapper: add a helper to generate numbers from " brian m. carlson
2022-01-04 21:01   ` Junio C Hamano
2022-01-04 21:39     ` Junio C Hamano
2022-01-04 23:12       ` brian m. carlson
2022-01-04 22:56     ` brian m. carlson
2022-01-04 23:17       ` Junio C Hamano
2022-01-04  1:55 ` [PATCH v2 2/2] wrapper: use a CSPRNG to generate random file names brian m. carlson
2022-01-04 12:44 ` [PATCH v2 0/2] Generate temporary files using a CSPRNG Johannes Schindelin
2022-01-17 21:56 ` [PATCH v3 " brian m. carlson
2022-01-17 21:56   ` [PATCH v3 1/2] wrapper: add a helper to generate numbers from " brian m. carlson
2022-01-18  9:45     ` Ævar Arnfjörð Bjarmason
2022-01-18 13:31       ` René Scharfe
2022-01-17 21:56   ` [PATCH v3 2/2] wrapper: use a CSPRNG to generate random file names brian m. carlson
2022-01-18  9:24     ` Ævar Arnfjörð Bjarmason
2022-01-18 14:42       ` René Scharfe
2022-01-18 14:51         ` Ævar Arnfjörð Bjarmason
2022-01-18 16:44           ` René Scharfe
2022-01-18 17:25         ` Junio C Hamano
2022-01-19 17:49           ` René Scharfe
2022-01-22 10:41           ` René Scharfe
2022-01-24 17:08             ` Junio C Hamano
2022-01-19  3:28       ` brian m. carlson

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).