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);
+}