diff --git a/CHANGELOG.md b/CHANGELOG.md index 41248bf6c..697642075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ This release focuses on analysisd scalability and broader stability. Key enhance - @atomicturtle - [PR 2238](https://github.com/ossec/ossec-hids/pull/2238) - Fix false dpkg install alert when removing packages - @atomicturtle - [PR 2239](https://github.com/ossec/ossec-hids/pull/2239) - Fix msauth Enterprise Admins false positives and negatives - @atomicturtle - [PR 2240](https://github.com/ossec/ossec-hids/pull/2240) - Fix Postfix rule 3331 false positive on postscreen client ports +- @atomicturtle - [PR 2243](https://github.com/ossec/ossec-hids/pull/2243) - Fix AIX agent entropy failure after chroot by keeping OS RNG usable **OSSEC changelog (4.1.0) ** diff --git a/src/addagent/manage_agents.c b/src/addagent/manage_agents.c index 7637453aa..2dfcab2a6 100644 --- a/src/addagent/manage_agents.c +++ b/src/addagent/manage_agents.c @@ -14,7 +14,6 @@ #include "manage_agents.h" #include "os_crypto/md5/md5_op.h" #include "external/cJSON/cJSON.h" -#include #include /* Global variables */ @@ -366,7 +365,7 @@ int add_agent(int json_output) char rand_hex[129]; /* Generate cryptographically secure random bytes */ - if (!RAND_bytes(random_data, sizeof(random_data))) { + if (!randombytes_try(random_data, sizeof(random_data))) { if (json_output) { cJSON *json_root = cJSON_CreateObject(); cJSON_AddNumberToObject(json_root, "error", 75); diff --git a/src/addagent/manage_keys.c b/src/addagent/manage_keys.c index c9acf3e8e..41c88cf6b 100644 --- a/src/addagent/manage_keys.c +++ b/src/addagent/manage_keys.c @@ -8,7 +8,6 @@ */ #include "manage_agents.h" -#include #include "os_crypto/md5/md5_op.h" #include "external/cJSON/cJSON.h" #include @@ -324,6 +323,7 @@ int k_extract(const char *cmdextract, int json_output) int k_bulkload(const char *cmdbulk) { int i = 1; + int status = 0; FILE *fp, *infp; char str1[STR_SIZE + 1]; char str2[STR_SIZE + 1]; @@ -443,29 +443,31 @@ int k_bulkload(const char *cmdbulk) } #endif - /* Cryptographically secure random number generation */ - unsigned char random_data[64]; - char rand_hex[129]; - - if (!RAND_bytes(random_data, sizeof(random_data))) { - merror("Failed to generate secure random data for agent key"); - return 0; - } + /* Cryptographically secure random number generation */ + unsigned char random_data[64]; + char rand_hex[129]; - /* Hex encode the random data */ - for(i=0; i<64; i++) { - sprintf(&rand_hex[i*2], "%02x", random_data[i]); - } + if (!randombytes_try(random_data, sizeof(random_data))) { + merror("Failed to generate secure random data for agent key"); + fclose(fp); + status = 1; + goto cleanup; + } - /* First key component: name + ID + random data */ - snprintf(str1, STR_SIZE, "%s%s%s", name, id, rand_hex); - OS_MD5_Str(str1, md1); + /* Hex encode the random data */ + for (i = 0; i < 64; i++) { + sprintf(&rand_hex[i * 2], "%02x", random_data[i]); + } + + /* First key component: name + ID + random data */ + snprintf(str1, STR_SIZE, "%s%s%s", name, id, rand_hex); + OS_MD5_Str(str1, md1); - /* Second key component: IP + name + random data (offset) */ - snprintf(str2, STR_SIZE, "%s%s%s", ip, name, rand_hex + 64); - OS_MD5_Str(str2, md2); + /* Second key component: IP + name + random data (offset) */ + snprintf(str2, STR_SIZE, "%s%s%s", ip, name, rand_hex + 64); + OS_MD5_Str(str2, md2); - fprintf(fp, "%s %s %s %s%s\n", id, name, ip, md1, md2); + fprintf(fp, "%s %s %s %s%s\n", id, name, ip, md1, md2); fclose(fp); printf(AGENT_ADD, id); @@ -473,8 +475,11 @@ int k_bulkload(const char *cmdbulk) cleanup: free(c_ip.ip); + if (status != 0) { + break; + } }; fclose(infp); - return (0); + return (status); } diff --git a/src/headers/randombytes.h b/src/headers/randombytes.h index 9c69de3f5..cfdabe92b 100644 --- a/src/headers/randombytes.h +++ b/src/headers/randombytes.h @@ -1,8 +1,11 @@ #ifndef __RANDOMBYTES_H #define __RANDOMBYTES_H +#include + void randombytes(void *ptr, size_t length); +/** Fill buffer with OS entropy. Returns 1 on success, 0 on failure (no exit). */ +int randombytes_try(void *ptr, size_t length); void srandom_init(void); #endif - diff --git a/src/os_crypto/aes/aes_op.c b/src/os_crypto/aes/aes_op.c index 4b47372a4..f2fe7abdc 100644 --- a/src/os_crypto/aes/aes_op.c +++ b/src/os_crypto/aes/aes_op.c @@ -18,12 +18,11 @@ #include #include #include "aes_op.h" +#include "randombytes.h" typedef unsigned char uchar; -#include - int OS_AES_Str(const char *input, char *output, const char *charkey, long size, short int action) { @@ -31,16 +30,23 @@ int OS_AES_Str(const char *input, char *output, const char *charkey, if(action == OS_ENCRYPT) { - /* Generate Random IV */ - if (!RAND_bytes(iv, sizeof(iv))) { - return 0; // Error + /* Per-message random IV via OS entropy (FD kept across chroot). */ + if (!randombytes_try(iv, sizeof(iv))) { + return 0; } /* Prepend IV to output */ memcpy(output, iv, 16); /* Encrypt content after the IV */ - return 16 + encrypt_AES((const uchar *)input, (int)size, (uchar *)charkey, iv, (uchar *)output + 16); + { + int aes_len = encrypt_AES((const uchar *)input, (int)size, (uchar *)charkey, iv, + (uchar *)output + 16); + if (aes_len == 0) { + return 0; + } + return 16 + aes_len; + } } else { diff --git a/src/os_crypto/shared/msgs.c b/src/os_crypto/shared/msgs.c index 53441070a..1c8e00551 100644 --- a/src/os_crypto/shared/msgs.c +++ b/src/os_crypto/shared/msgs.c @@ -13,7 +13,6 @@ #include "os_crypto/md5/md5_op.h" #include "os_crypto/blowfish/bf_op.h" #include "os_crypto/aes/aes_op.h" -#include #include /* Helper for File_Inode */ @@ -544,10 +543,15 @@ size_t CreateSecMSG(const keystore *keys, const char *msg, size_t msg_length, ch return 0; // OS_INVALID } - /* Random number, take only 5 chars ~= 2^16=65536*/ - if (!RAND_bytes((unsigned char *)&rand1, sizeof(rand1))) { - merror("RAND_bytes failed"); - return(0); + /* Random number, take only 5 chars ~= 2^16=65536. + * Entropy via randombytes_try: pre-chroot /dev/urandom FD on Unix, + * arc4random on OpenBSD, CryptoAPI (CRYPT_VERIFYCONTEXT) on Windows. + * Avoids OpenSSL RAND_bytes, which re-opens /dev/urandom by path after + * chroot and fails on AIX and similar platforms without getrandom(). + */ + if (!randombytes_try(&rand1, sizeof(rand1))) { + merror("randombytes failed"); + return (0); } _tmpmsg[OS_MAXSTR + 1] = '\0'; @@ -643,6 +647,14 @@ size_t CreateSecMSG(const keystore *keys, const char *msg, size_t msg_length, ch (long) cmp_size, OS_ENCRYPT,crypto_method); + /* Blowfish returns 1; AES returns ciphertext length. Zero means failure + * (e.g. AES IV entropy unavailable). + */ + if (!crypto_length) { + merror("encryption failed"); + return (0); + } + if(cmp_size < crypto_length) cmp_size = crypto_length; diff --git a/src/shared/randombytes.c b/src/shared/randombytes.c index 2a72138cf..2bf6a3772 100644 --- a/src/shared/randombytes.c +++ b/src/shared/randombytes.c @@ -1,6 +1,10 @@ #ifndef WIN32 #include #include +#include +#include +#include +#include #endif #include @@ -8,35 +12,206 @@ #include "shared.h" +#ifdef LIBOPENSSL_ENABLED +#include +#include +#endif -void randombytes(void *ptr, size_t length) +/* Cap how long we hold the entropy FD mutex waiting on /dev/random. */ +#ifndef RANDOMBYTES_READ_TIMEOUT_MS +#define RANDOMBYTES_READ_TIMEOUT_MS 5000 +#endif + +#if !defined(WIN32) && !defined(__OpenBSD__) +#ifndef O_CLOEXEC +#define RB_O_CLOEXEC 0 +#else +#define RB_O_CLOEXEC O_CLOEXEC +#endif + +/* Process-wide entropy FD + mutex (must outlive randombytes_try frames for + * pthread_atfork handlers). Kept open across chroot; never closed on transient + * read failures so post-chroot reopen-by-path is not required. + */ +static int rb_fh = -1; +static pthread_mutex_t rb_fh_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_once_t rb_atfork_once = PTHREAD_ONCE_INIT; + +static void rb_atfork_prepare(void) { - char failed = 0; + pthread_mutex_lock(&rb_fh_mutex); +} + +static void rb_atfork_parent(void) +{ + pthread_mutex_unlock(&rb_fh_mutex); +} + +static void rb_atfork_child(void) +{ + /* prepare() held the lock across fork; drop it in the child copy. */ + pthread_mutex_unlock(&rb_fh_mutex); +} + +static void rb_register_atfork(void) +{ + (void)pthread_atfork(rb_atfork_prepare, rb_atfork_parent, rb_atfork_child); +} + +static void rb_atfork_setup(void) +{ + (void)pthread_once(&rb_atfork_once, rb_register_atfork); +} + +static int rb_open_entropy(void) +{ + int fd; + + fd = open("/dev/urandom", O_RDONLY | RB_O_CLOEXEC); + if (fd < 0) { + fd = open("/dev/random", O_RDONLY | RB_O_CLOEXEC); + } +#ifndef O_CLOEXEC + if (fd >= 0) { + int flags = fcntl(fd, F_GETFD); + if (flags >= 0) { + (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + } + } +#endif + return fd; +} +#endif /* !WIN32 && !__OpenBSD__ */ + + +int randombytes_try(void *ptr, size_t length) +{ + if (length == 0) { + return 1; + } + if (ptr == NULL) { + return 0; + } #ifdef WIN32 static HCRYPTPROV prov = 0; + static CRITICAL_SECTION prov_lock; + static volatile LONG lock_ready = 0; + int ok; + + /* One-time CRITICAL_SECTION init without races. */ + if (lock_ready != 2) { + if (InterlockedCompareExchange(&lock_ready, 1, 0) == 0) { + InitializeCriticalSection(&prov_lock); + InterlockedExchange(&lock_ready, 2); + } else { + while (InterlockedCompareExchange(&lock_ready, 2, 2) != 2) { + Sleep(1); + } + } + } + + EnterCriticalSection(&prov_lock); + + /* CRYPT_VERIFYCONTEXT: ephemeral randomness, no key container required. + * Avoids NTE_BAD_KEYSET on accounts without a default container. + */ if (prov == 0) { - if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, 0)) { - failed = 1; + if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + LeaveCriticalSection(&prov_lock); + return 0; } } - if (!failed && !CryptGenRandom(prov, length, ptr)) { - failed = 1; + + /* CryptGenRandom takes a DWORD length; reject oversized requests so a + * truncated cast cannot report success after a partial fill. + */ + if (length > (size_t)MAXDWORD) { + LeaveCriticalSection(&prov_lock); + return 0; } + + ok = CryptGenRandom(prov, (DWORD)length, (BYTE *)ptr) ? 1 : 0; + LeaveCriticalSection(&prov_lock); + return ok; +#elif defined(__OpenBSD__) + /* LibreSSL RAND_bytes used arc4random; keep that path so chroot never + * depends on /dev/urandom device nodes inside the jail. + */ + arc4random_buf(ptr, length); + return 1; #else - static int fh = -1; + unsigned char *p = (unsigned char *)ptr; + size_t remaining = length; + + /* Keep the entropy FD open across chroot: open once before Privsep_Chroot + * (via srandom_init), then reuse the FD after jail. Re-opening /dev/urandom + * by path after chroot fails on platforms without getrandom() (e.g. AIX). + * Once open succeeds, keep the FD even on transient poll/read failures so + * post-chroot callers are not forced to reopen by path. Mutex covers + * open/read so remoted/agent worker threads cannot race. poll() bounds how + * long a starved /dev/random can hold that lock. pthread_atfork avoids a + * permanently locked mutex in a forked child. + */ + rb_atfork_setup(); + pthread_mutex_lock(&rb_fh_mutex); + + if (rb_fh < 0) { + rb_fh = rb_open_entropy(); + } + + if (rb_fh < 0) { + pthread_mutex_unlock(&rb_fh_mutex); + return 0; + } - if (fh >= 0 || (fh = open("/dev/urandom", O_RDONLY)) >= 0 || (fh = open("/dev/random", O_RDONLY)) >= 0) { - const ssize_t ret = read(fh, ptr, length); - if (ret < 0 || (size_t) ret != length) { - failed = 1; + while (remaining > 0) { + struct pollfd pfd; + int pret; + ssize_t ret; + + pfd.fd = rb_fh; + pfd.events = POLLIN; + pret = poll(&pfd, 1, RANDOMBYTES_READ_TIMEOUT_MS); + if (pret == 0) { + pthread_mutex_unlock(&rb_fh_mutex); + return 0; + } + if (pret < 0) { + if (errno == EINTR) { + continue; + } + pthread_mutex_unlock(&rb_fh_mutex); + return 0; + } + + ret = read(rb_fh, p, remaining); + + if (ret < 0) { + if (errno == EINTR) { + continue; + } + pthread_mutex_unlock(&rb_fh_mutex); + return 0; + } + if (ret == 0) { + /* Unexpected EOF on a random device; keep FD for later retries. */ + pthread_mutex_unlock(&rb_fh_mutex); + return 0; } - } else { - failed = 1; + + p += (size_t)ret; + remaining -= (size_t)ret; } + + pthread_mutex_unlock(&rb_fh_mutex); + return 1; #endif +} - if (failed) { +void randombytes(void *ptr, size_t length) +{ + if (!randombytes_try(ptr, length)) { ErrorExit("%s: ERROR: randombytes failed for all possible methods for accessing random data", __local_name); } } @@ -44,6 +219,9 @@ void randombytes(void *ptr, size_t length) void srandom_init(void) { #ifndef WIN32 +#if !defined(__OpenBSD__) + rb_atfork_setup(); +#endif #ifdef __OpenBSD__ srandomdev(); #else @@ -51,6 +229,16 @@ void srandom_init(void) randombytes(&seed, sizeof seed); srandom(seed); #endif /* !__OpenBSD__ */ +#ifdef LIBOPENSSL_ENABLED + /* Seed OpenSSL before chroot so TLS/authd do not re-open /dev/urandom + * by path inside the jail. Keep device FDs open across reseed on 1.1.1+. + */ +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) + RAND_keep_random_devices_open(1); +#endif + if (RAND_poll() != 1 || RAND_status() != 1) { + merror("%s: ERROR: OpenSSL RNG failed to seed before chroot", __local_name); + } +#endif /* LIBOPENSSL_ENABLED */ #endif /* !WIN32 */ } -