diff --git a/build_windows/VS10/db.vcxproj b/build_windows/VS10/db.vcxproj index b4c784d86..8f8172fdc 100644 --- a/build_windows/VS10/db.vcxproj +++ b/build_windows/VS10/db.vcxproj @@ -340,6 +340,7 @@ + diff --git a/build_windows/VS10/db_small.vcxproj b/build_windows/VS10/db_small.vcxproj index 565561c48..01ec6343d 100644 --- a/build_windows/VS10/db_small.vcxproj +++ b/build_windows/VS10/db_small.vcxproj @@ -316,6 +316,7 @@ + diff --git a/dist/Makefile.in b/dist/Makefile.in index b821770d5..70722af76 100644 --- a/dist/Makefile.in +++ b/dist/Makefile.in @@ -291,7 +291,7 @@ DTRACE_OBJS= @ADDITIONAL_OBJS@ @REPLACEMENT_OBJS@ @CRYPTO_OBJS@ \ mp_fmethod@o@ mp_fopen@o@ mp_fput@o@ mp_fset@o@ mp_method@o@ \ mp_mvcc@o@ mp_region@o@ mp_register@o@ mp_resize@o@ mp_stat@o@ \ mp_sync@o@ mp_trickle@o@ openflags@o@ os_abort@o@ os_abs@o@ os_aio@o@ os_aio_iocp@o@ os_aio_kqueue@o@ os_aio_pool@o@ os_aio_posix@o@ os_aio_uring@o@ \ - os_alloc@o@ os_atomic@o@ os_clock@o@ os_cpu@o@ os_ctime@o@ os_config@o@ \ + os_alloc@o@ os_atomic@o@ os_clock@o@ os_cpu@o@ os_csprng@o@ os_ctime@o@ os_config@o@ \ os_dir@o@ os_errno@o@ os_fid@o@ os_flock@o@ os_fsync@o@ \ os_getenv@o@ os_handle@o@ os_map@o@ os_method@o@ os_mkdir@o@ \ os_open@o@ os_path@o@ os_pid@o@ os_rename@o@ os_root@o@ \ @@ -2705,6 +2705,8 @@ os_clock@o@: $(srcdir)/@OSDIR@/os_clock.c $(CC) $(CFLAGS) $(DEPFLAGS) $< os_config@o@: $(srcdir)/@OSDIR@/os_config.c $(CC) $(CFLAGS) $(DEPFLAGS) $< +os_csprng@o@: $(srcdir)/os/os_csprng.c + $(CC) $(CFLAGS) $(DEPFLAGS) $< os_cpu@o@: $(srcdir)/@OSDIR@/os_cpu.c $(CC) $(CFLAGS) $(DEPFLAGS) $< os_ctime@o@: $(srcdir)/os/os_ctime.c diff --git a/dist/configure b/dist/configure index 94bc4d4c0..9c9a73ec0 100755 --- a/dist/configure +++ b/dist/configure @@ -22094,6 +22094,12 @@ if test "x$ac_cv_header_execinfo_h" = xyes then : printf '%s\n' "#define HAVE_EXECINFO_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "sys/random.h" "ac_cv_header_sys_random_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_random_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_RANDOM_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "sys/select.h" "ac_cv_header_sys_select_h" "$ac_includes_default" if test "x$ac_cv_header_sys_select_h" = xyes @@ -27320,6 +27326,23 @@ then : fi +# Cryptographically strong OS entropy, used to seed encryption IVs. +# getrandom(2) is Linux/glibc 2.25+; arc4random_buf(3) is the BSDs and macOS. +# Neither is required: os_csprng.c falls back to reading /dev/urandom. +ac_fn_c_check_func "$LINENO" "getrandom" "ac_cv_func_getrandom" +if test "x$ac_cv_func_getrandom" = xyes +then : + printf '%s\n' "#define HAVE_GETRANDOM 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "arc4random_buf" "ac_cv_func_arc4random_buf" +if test "x$ac_cv_func_arc4random_buf" = xyes +then : + printf '%s\n' "#define HAVE_ARC4RANDOM_BUF 1" >>confdefs.h + +fi + + ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday" diff --git a/dist/configure.ac b/dist/configure.ac index 869ccc45a..856599068 100644 --- a/dist/configure.ac +++ b/dist/configure.ac @@ -742,7 +742,7 @@ fi AC_HEADER_STAT AC_CHECK_HEADERS([sys/time.h time.h]) AC_HEADER_DIRENT -AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h sys/time.h) +AC_CHECK_HEADERS(execinfo.h sys/random.h sys/select.h sys/socket.h sys/time.h) AC_CHECK_MEMBERS([struct stat.st_blksize]) AM_TYPES @@ -847,6 +847,11 @@ AC_CHECK_FUNCS(\ pthread_self pthread_yield random sched_yield select setgid setuid\ sigaction snprintf stat sysconf vsnprintf yield) +# Cryptographically strong OS entropy, used to seed encryption IVs. +# getrandom(2) is Linux/glibc 2.25+; arc4random_buf(3) is the BSDs and macOS. +# Neither is required: os_csprng.c falls back to reading /dev/urandom. +AC_CHECK_FUNCS(getrandom arc4random_buf) + AC_TIMERS # Ftruncate. diff --git a/dist/meson.build b/dist/meson.build index f6c0ca7bd..60e5262aa 100644 --- a/dist/meson.build +++ b/dist/meson.build @@ -327,6 +327,7 @@ libdb_srcnames = [ 'src/os/os_atomic.c', 'src/os/os_clock.c', 'src/os/os_config.c', + 'src/os/os_csprng.c', 'src/os/os_cpu.c', 'src/os/os_ctime.c', 'src/os/os_dir.c', diff --git a/dist/srcfiles.in b/dist/srcfiles.in index f91288609..db3fa2632 100644 --- a/dist/srcfiles.in +++ b/dist/srcfiles.in @@ -230,6 +230,7 @@ src/os/os_aio_iocp.c android src/os/os_alloc.c android src/os/os_clock.c android src/os/os_config.c android +src/os/os_csprng.c android src/os/os_cpu.c android src/os/os_ctime.c android src/os/os_dir.c android diff --git a/src/common/db_err.c b/src/common/db_err.c index e21fa1c09..c674263cc 100644 --- a/src/common/db_err.c +++ b/src/common/db_err.c @@ -357,10 +357,10 @@ db_strerror(error) return (DB_STR("0088", "DB_SECONDARY_BAD: Secondary index inconsistent with primary")); case DB_SNAPSHOT_CONFLICT: - return (DB_STR("4573", + return (DB_STR("0211", "DB_SNAPSHOT_CONFLICT: Serializable snapshot update conflict")); case DB_SNAPSHOT_UNSAFE: - return (DB_STR("4574", + return (DB_STR("0212", "DB_SNAPSHOT_UNSAFE: Potential serializable snapshot anomaly")); case DB_TIMEOUT: return (DB_STR("0089", "DB_TIMEOUT: Operation timed out")); diff --git a/src/crypto/mersenne/mt19937db.c b/src/crypto/mersenne/mt19937db.c index 62076dfe0..1a57f4eb1 100644 --- a/src/crypto/mersenne/mt19937db.c +++ b/src/crypto/mersenne/mt19937db.c @@ -154,14 +154,20 @@ __db_genrand(env) if (env->mti == N+1) { /* if sgenrand() has not been called, */ /* - * Seed the generator with the hashed time. The __db_mac - * function will return 4 bytes if we don't send in a key. + * Seed the generator from the OS entropy source. This used to + * hash the wall-clock seconds, which is low-entropy and + * guessable -- an attacker who knows roughly when the + * environment was created could narrow the IV stream. Fall + * back to the hashed clock only if the OS has no entropy + * source at all, so encryption still functions on such a + * platform (with the historical, weaker seeding). */ - do { - __os_gettime(env, &ts, 1); - __db_chksum(NULL, (u_int8_t *)&ts.tv_sec, - sizeof(ts.tv_sec), NULL, (u_int8_t *)&seed); - } while (seed == 0); + if (__os_csprng(env, &seed, sizeof(seed)) != 0 || seed == 0) + do { + __os_gettime(env, &ts, 1); + __db_chksum(NULL, (u_int8_t *)&ts.tv_sec, + sizeof(ts.tv_sec), NULL, (u_int8_t *)&seed); + } while (seed == 0); __db_sgenrand((unsigned long)seed, env->mt, &env->mti); } diff --git a/src/dbinc/db_int.in b/src/dbinc/db_int.in index 21d828df7..018ffbc97 100644 --- a/src/dbinc/db_int.in +++ b/src/dbinc/db_int.in @@ -25,6 +25,16 @@ #endif #endif +/* + * getrandom(2) is declared in on glibc and in on + * some others; include the header when we have it so os_csprng.c can call it. + */ +#ifdef HAVE_GETRANDOM +#ifdef HAVE_SYS_RANDOM_H +#include +#endif +#endif + #if TIME_WITH_SYS_TIME #include #include diff --git a/src/dbinc_auto/os_ext.h b/src/dbinc_auto/os_ext.h index 41a28fb0a..9be5a7a01 100644 --- a/src/dbinc_auto/os_ext.h +++ b/src/dbinc_auto/os_ext.h @@ -320,6 +320,7 @@ int __os_support_direct_io __P((void)); int __os_support_db_register __P((void)); int __os_support_replication __P((void)); u_int32_t __os_cpu_count __P((void)); +int __os_csprng __P((ENV *, void *, size_t)); char *__os_ctime __P((const time_t *, char *)); int __os_dirlist __P((ENV *, const char *, int, char ***, int *)); void __os_dirfree __P((ENV *, char **, int)); diff --git a/src/os/os_csprng.c b/src/os/os_csprng.c new file mode 100644 index 000000000..9b90c3b04 --- /dev/null +++ b/src/os/os_csprng.c @@ -0,0 +1,109 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 2026 The libdb contributors. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +/* + * __os_csprng -- + * Fill a buffer with cryptographically strong random bytes from the + * operating system. + * + * This is the entropy source for security-sensitive values (currently + * encryption initialization vectors). It is deliberately separate from + * the Mersenne Twister in src/crypto/mersenne: that generator is fine for + * sequence quality but is not a CSPRNG and was historically seeded from + * hashed wall-clock seconds, which is guessable. + * + * Returns 0 on success, or a non-zero error if no OS entropy source is + * available; callers MUST handle failure rather than silently falling back + * to a weak source. + * + * PUBLIC: int __os_csprng __P((ENV *, void *, size_t)); + */ +int +__os_csprng(env, buf, len) + ENV *env; + void *buf; + size_t len; +{ + u_int8_t *p; + size_t need; +#if defined(HAVE_GETRANDOM) + ssize_t n; +#endif +#if !defined(HAVE_ARC4RANDOM_BUF) + DB_FH *fhp; + size_t nr; + int ret; +#endif + + p = buf; + need = len; + +#ifdef HAVE_GETRANDOM + /* + * Linux (glibc 2.25+) / others: getrandom(2). It can return a short + * read, and can be interrupted, so loop. GRND_NONBLOCK is NOT used: + * we would rather block briefly at first use than fail or fall back to + * a weak source. + */ + while (need > 0) { + n = getrandom(p, need, 0); + if (n < 0) { + if (__os_get_syserr() == EINTR) + continue; + break; /* Fall through to /dev/urandom. */ + } + p += n; + need -= (size_t)n; + } + if (need == 0) + return (0); + /* Reset and try the device. */ + p = buf; + need = len; +#endif + +#ifdef HAVE_ARC4RANDOM_BUF + /* + * The BSDs and macOS: arc4random_buf() cannot fail and needs no fd. + */ + arc4random_buf(p, need); + return (0); +#else + /* + * Portable fallback: read /dev/urandom. Use the OS layer so the file + * handling matches the rest of the library (DB_FH handle, not a raw fd). + */ + if ((ret = __os_open(env, "/dev/urandom", 0, + DB_OSO_RDONLY, DB_MODE_600, &fhp)) != 0) + return (ret); + + while (need > 0) { + nr = 0; + if ((ret = __os_read(env, fhp, p, need, &nr)) != 0) { + (void)__os_closehandle(env, fhp); + return (ret); + } + if (nr == 0) /* Unexpected EOF. */ + break; + p += nr; + need -= nr; + } + (void)__os_closehandle(env, fhp); + + if (need != 0) { + __db_errx(env, DB_STR("0213", + "Unable to obtain random bytes from the operating system")); + return (EIO); + } + return (0); +#endif +} diff --git a/src/os_windows/os_csprng.c b/src/os_windows/os_csprng.c new file mode 100644 index 000000000..f91a56deb --- /dev/null +++ b/src/os_windows/os_csprng.c @@ -0,0 +1,64 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 2026 The libdb contributors. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +/* + * __os_csprng -- + * Fill a buffer with cryptographically strong random bytes from the + * operating system (Windows). + * + * This is the Windows counterpart of src/os/os_csprng.c and the entropy + * source for security-sensitive values (currently encryption + * initialization vectors). RtlGenRandom (exposed as SystemFunction036) + * is used rather than BCryptGenRandom: it needs no provider handle, has + * been available since Windows XP, and avoids linking bcrypt.lib. + * + * Returns 0 on success, or a non-zero error if the OS declines; callers + * MUST handle failure rather than silently falling back to a weak source. + * + * PUBLIC: int __os_csprng __P((ENV *, void *, size_t)); + */ +int +__os_csprng(env, buf, len) + ENV *env; + void *buf; + size_t len; +{ + /* + * RtlGenRandom is declared as SystemFunction036 in ntsecapi.h, which + * pulls in a large amount of unrelated interface; declare it directly + * the way Microsoft documents for this use. + */ + BOOLEAN (APIENTRY *pRtlGenRandom)(PVOID, ULONG); + HMODULE advapi; + int ret; + + if (len == 0) + return (0); + + ret = 0; + if ((advapi = LoadLibraryA("advapi32.dll")) == NULL) + goto err; + + pRtlGenRandom = (BOOLEAN (APIENTRY *)(PVOID, ULONG)) + GetProcAddress(advapi, "SystemFunction036"); + if (pRtlGenRandom == NULL || + !pRtlGenRandom(buf, (ULONG)len)) + ret = EIO; + + (void)FreeLibrary(advapi); + if (ret == 0) + return (0); + +err: __db_errx(env, DB_STR("0214", + "Unable to obtain random bytes from the operating system")); + return (ret == 0 ? EIO : ret); +}