/* * Copyright 2001-2021 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include "e_os.h" #if defined(OPENSSL_SYS_VMS) # define __NEW_STARLET 1 /* New starlet definitions since VMS 7.0 */ # include # include "internal/cryptlib.h" # include # include # include # include "crypto/rand.h" # include "rand_local.h" # include # include # include # include # include # include # include # include # include # include # include # include # ifdef __DECC # pragma message disable DOLLARID # endif # include /* SYS$GET_ENTROPY presence */ # ifndef OPENSSL_RAND_SEED_OS # error "Unsupported seeding method configured; must be os" # endif /* * DATA COLLECTION METHOD * ====================== * * This is a method to get low quality entropy. * It works by collecting all kinds of statistical data that * VMS offers and using them as random seed. */ /* We need to make sure we have the right size pointer in some cases */ # if __INITIAL_POINTER_SIZE == 64 # pragma pointer_size save # pragma pointer_size 32 # endif typedef uint32_t *uint32_t__ptr32; # if __INITIAL_POINTER_SIZE == 64 # pragma pointer_size restore # endif struct item_st { short length, code; /* length is number of bytes */ }; static const struct item_st DVI_item_data[] = { {4, DVI$_ERRCNT}, {4, DVI$_REFCNT}, }; static const struct item_st JPI_item_data[] = { {4, JPI$_BUFIO}, {4, JPI$_CPUTIM}, {4, JPI$_DIRIO}, {4, JPI$_IMAGECOUNT}, {4, JPI$_PAGEFLTS}, {4, JPI$_PID}, {4, JPI$_PPGCNT}, {4, JPI$_WSPEAK}, /* * Note: the direct result is just a 32-bit address. However, it points * to a list of 4 32-bit words, so we make extra space for them so we can * do in-place replacement of values */ {16, JPI$_FINALEXC}, }; static const struct item_st JPI_item_data_64bit[] = { {8, JPI$_LAST_LOGIN_I}, {8, JPI$_LOGINTIM}, }; static const struct item_st RMI_item_data[] = { {4, RMI$_COLPG}, {4, RMI$_MWAIT}, {4, RMI$_CEF}, {4, RMI$_PFW}, {4, RMI$_LEF}, {4, RMI$_LEFO}, {4, RMI$_HIB}, {4, RMI$_HIBO}, {4, RMI$_SUSP}, {4, RMI$_SUSPO}, {4, RMI$_FPG}, {4, RMI$_COM}, {4, RMI$_COMO}, {4, RMI$_CUR}, #if defined __alpha {4, RMI$_FRLIST}, {4, RMI$_MODLIST}, #endif {4, RMI$_FAULTS}, {4, RMI$_PREADS}, {4, RMI$_PWRITES}, {4, RMI$_PWRITIO}, {4, RMI$_PREADIO}, {4, RMI$_GVALFLTS}, {4, RMI$_WRTINPROG}, {4, RMI$_FREFLTS}, {4, RMI$_DZROFLTS}, {4, RMI$_SYSFAULTS}, {4, RMI$_ISWPCNT}, {4, RMI$_DIRIO}, {4, RMI$_BUFIO}, {4, RMI$_MBREADS}, {4, RMI$_MBWRITES}, {4, RMI$_LOGNAM}, {4, RMI$_FCPCALLS}, {4, RMI$_FCPREAD}, {4, RMI$_FCPWRITE}, {4, RMI$_FCPCACHE}, {4, RMI$_FCPCPU}, {4, RMI$_FCPHIT}, {4, RMI$_FCPSPLIT}, {4, RMI$_FCPFAULT}, {4, RMI$_ENQNEW}, {4, RMI$_ENQCVT}, {4, RMI$_DEQ}, {4, RMI$_BLKAST}, {4, RMI$_ENQWAIT}, {4, RMI$_ENQNOTQD}, {4, RMI$_DLCKSRCH}, {4, RMI$_DLCKFND}, {4, RMI$_NUMLOCKS}, {4, RMI$_NUMRES}, {4, RMI$_ARRLOCPK}, {4, RMI$_DEPLOCPK}, {4, RMI$_ARRTRAPK}, {4, RMI$_TRCNGLOS}, {4, RMI$_RCVBUFFL}, {4, RMI$_ENQNEWLOC}, {4, RMI$_ENQNEWIN}, {4, RMI$_ENQNEWOUT}, {4, RMI$_ENQCVTLOC}, {4, RMI$_ENQCVTIN}, {4, RMI$_ENQCVTOUT}, {4, RMI$_DEQLOC}, {4, RMI$_DEQIN}, {4, RMI$_DEQOUT}, {4, RMI$_BLKLOC}, {4, RMI$_BLKIN}, {4, RMI$_BLKOUT}, {4, RMI$_DIRIN}, {4, RMI$_DIROUT}, /* We currently get a fault when trying these. TODO: To be figured out. */ #if 0 {140, RMI$_MSCP_EVERYTHING}, /* 35 32-bit words */ {152, RMI$_DDTM_ALL}, /* 38 32-bit words */ {80, RMI$_TMSCP_EVERYTHING} /* 20 32-bit words */ #endif {4, RMI$_LPZ_PAGCNT}, {4, RMI$_LPZ_HITS}, {4, RMI$_LPZ_MISSES}, {4, RMI$_LPZ_EXPCNT}, {4, RMI$_LPZ_ALLOCF}, {4, RMI$_LPZ_ALLOC2}, {4, RMI$_ACCESS}, {4, RMI$_ALLOC}, {4, RMI$_FCPCREATE}, {4, RMI$_VOLWAIT}, {4, RMI$_FCPTURN}, {4, RMI$_FCPERASE}, {4, RMI$_OPENS}, {4, RMI$_FIDHIT}, {4, RMI$_FIDMISS}, {4, RMI$_FILHDR_HIT}, {4, RMI$_DIRFCB_HIT}, {4, RMI$_DIRFCB_MISS}, {4, RMI$_DIRDATA_HIT}, {4, RMI$_EXTHIT}, {4, RMI$_EXTMISS}, {4, RMI$_QUOHIT}, {4, RMI$_QUOMISS}, {4, RMI$_STORAGMAP_HIT}, {4, RMI$_VOLLCK}, {4, RMI$_SYNCHLCK}, {4, RMI$_SYNCHWAIT}, {4, RMI$_ACCLCK}, {4, RMI$_XQPCACHEWAIT}, {4, RMI$_DIRDATA_MISS}, {4, RMI$_FILHDR_MISS}, {4, RMI$_STORAGMAP_MISS}, {4, RMI$_PROCCNTMAX}, {4, RMI$_PROCBATCNT}, {4, RMI$_PROCINTCNT}, {4, RMI$_PROCNETCNT}, {4, RMI$_PROCSWITCHCNT}, {4, RMI$_PROCBALSETCNT}, {4, RMI$_PROCLOADCNT}, {4, RMI$_BADFLTS}, {4, RMI$_EXEFAULTS}, {4, RMI$_HDRINSWAPS}, {4, RMI$_HDROUTSWAPS}, {4, RMI$_IOPAGCNT}, {4, RMI$_ISWPCNTPG}, {4, RMI$_OSWPCNT}, {4, RMI$_OSWPCNTPG}, {4, RMI$_RDFAULTS}, {4, RMI$_TRANSFLTS}, {4, RMI$_WRTFAULTS}, #if defined __alpha {4, RMI$_USERPAGES}, #endif {4, RMI$_VMSPAGES}, {4, RMI$_TTWRITES}, {4, RMI$_BUFOBJPAG}, {4, RMI$_BUFOBJPAGPEAK}, {4, RMI$_BUFOBJPAGS01}, {4, RMI$_BUFOBJPAGS2}, {4, RMI$_BUFOBJPAGMAXS01}, {4, RMI$_BUFOBJPAGMAXS2}, {4, RMI$_BUFOBJPAGPEAKS01}, {4, RMI$_BUFOBJPAGPEAKS2}, {4, RMI$_BUFOBJPGLTMAXS01}, {4, RMI$_BUFOBJPGLTMAXS2}, {4, RMI$_DLCK_INCMPLT}, {4, RMI$_DLCKMSGS_IN}, {4, RMI$_DLCKMSGS_OUT}, {4, RMI$_MCHKERRS}, {4, RMI$_MEMERRS}, }; static const struct item_st RMI_item_data_64bit[] = { #if defined __ia64 {8, RMI$_FRLIST}, {8, RMI$_MODLIST}, #endif {8, RMI$_LCKMGR_REQCNT}, {8, RMI$_LCKMGR_REQTIME}, {8, RMI$_LCKMGR_SPINCNT}, {8, RMI$_LCKMGR_SPINTIME}, {8, RMI$_CPUINTSTK}, {8, RMI$_CPUMPSYNCH}, {8, RMI$_CPUKERNEL}, {8, RMI$_CPUEXEC}, {8, RMI$_CPUSUPER}, {8, RMI$_CPUUSER}, #if defined __ia64 {8, RMI$_USERPAGES}, #endif {8, RMI$_TQETOTAL}, {8, RMI$_TQESYSUB}, {8, RMI$_TQEUSRTIMR}, {8, RMI$_TQEUSRWAKE}, }; static const struct item_st SYI_item_data[] = { {4, SYI$_PAGEFILE_FREE}, }; /* * Input: * items_data - an array of lengths and codes * items_data_num - number of elements in that array * * Output: * items - pre-allocated ILE3 array to be filled. * It's assumed to have items_data_num elements plus * one extra for the terminating NULL element * databuffer - pre-allocated 32-bit word array. * * Returns the number of elements used in databuffer */ static size_t prepare_item_list(const struct item_st *items_input, size_t items_input_num, ILE3 *items, uint32_t__ptr32 databuffer) { size_t data_sz = 0; for (; items_input_num-- > 0; items_input++, items++) { items->ile3$w_code = items_input->code; /* Special treatment of JPI$_FINALEXC */ if (items->ile3$w_code == JPI$_FINALEXC) items->ile3$w_length = 4; else items->ile3$w_length = items_input->length; items->ile3$ps_bufaddr = databuffer; items->ile3$ps_retlen_addr = 0; databuffer += items_input->length / sizeof(databuffer[0]); data_sz += items_input->length; } /* Terminating NULL entry */ items->ile3$w_length = items->ile3$w_code = 0; items->ile3$ps_bufaddr = items->ile3$ps_retlen_addr = NULL; return data_sz / sizeof(databuffer[0]); } static void massage_JPI(ILE3 *items) { /* * Special treatment of JPI$_FINALEXC * The result of that item's data buffer is a 32-bit address to a list of * 4 32-bit words. */ for (; items->ile3$w_length != 0; items++) { if (items->ile3$w_code == JPI$_FINALEXC) { uint32_t *data = items->ile3$ps_bufaddr; uint32_t *ptr = (uint32_t *)*data; size_t j; /* * We know we made space for 4 32-bit words, so we can do in-place * replacement. */ for (j = 0; j < 4; j++) data[j] = ptr[j]; break; } } } /* * This number expresses how many bits of data contain 1 bit of entropy. * * For the moment, we assume about 0.05 entropy bits per data bit, or 1 * bit of entropy per 20 data bits. */ #define ENTROPY_FACTOR 20 size_t data_collect_method(RAND_POOL *pool) { ILE3 JPI_items_64bit[OSSL_NELEM(JPI_item_data_64bit) + 1]; ILE3 RMI_items_64bit[OSSL_NELEM(RMI_item_data_64bit) + 1]; ILE3 DVI_items[OSSL_NELEM(DVI_item_data) + 1]; ILE3 JPI_items[OSSL_NELEM(JPI_item_data) + 1]; ILE3 RMI_items[OSSL_NELEM(RMI_item_data) + 1]; ILE3 SYI_items[OSSL_NELEM(SYI_item_data) + 1]; union { /* This ensures buffer starts at 64 bit boundary */ uint64_t dummy; uint32_t buffer[OSSL_NELEM(JPI_item_data_64bit) * 2 + OSSL_NELEM(RMI_item_data_64bit) * 2 + OSSL_NELEM(DVI_item_data) + OSSL_NELEM(JPI_item_data) + OSSL_NELEM(RMI_item_data) + OSSL_NELEM(SYI_item_data) + 4 /* For JPI$_FINALEXC */]; } data; size_t total_elems = 0; size_t total_length = 0; size_t bytes_needed = rand_pool_bytes_needed(pool, ENTROPY_FACTOR); size_t bytes_remaining = rand_pool_bytes_remaining(pool); /* Take all the 64-bit items first, to ensure proper alignment of data */ total_elems += prepare_item_list(JPI_item_data_64bit, OSSL_NELEM(JPI_item_data_64bit), JPI_items_64bit, &data.buffer[total_elems]); total_elems += prepare_item_list(RMI_item_data_64bit, OSSL_NELEM(RMI_item_data_64bit), RMI_items_64bit, &data.buffer[total_elems]); /* Now the 32-bit items */ total_elems += prepare_item_list(DVI_item_data, OSSL_NELEM(DVI_item_data), DVI_items, &data.buffer[total_elems]); total_elems += prepare_item_list(JPI_item_data, OSSL_NELEM(JPI_item_data), JPI_items, &data.buffer[total_elems]); total_elems += prepare_item_list(RMI_item_data, OSSL_NELEM(RMI_item_data), RMI_items, &data.buffer[total_elems]); total_elems += prepare_item_list(SYI_item_data, OSSL_NELEM(SYI_item_data), SYI_items, &data.buffer[total_elems]); total_length = total_elems * sizeof(data.buffer[0]); /* Fill data.buffer with various info bits from this process */ { uint32_t status; uint32_t efn; IOSB iosb; $DESCRIPTOR(SYSDEVICE,"SYS$SYSDEVICE:"); if ((status = sys$getdviw(EFN$C_ENF, 0, &SYSDEVICE, DVI_items, 0, 0, 0, 0, 0)) != SS$_NORMAL) { lib$signal(status); return 0; } if ((status = sys$getjpiw(EFN$C_ENF, 0, 0, JPI_items_64bit, 0, 0, 0)) != SS$_NORMAL) { lib$signal(status); return 0; } if ((status = sys$getjpiw(EFN$C_ENF, 0, 0, JPI_items, 0, 0, 0)) != SS$_NORMAL) { lib$signal(status); return 0; } if ((status = sys$getsyiw(EFN$C_ENF, 0, 0, SYI_items, 0, 0, 0)) != SS$_NORMAL) { lib$signal(status); return 0; } /* * The RMI service is a bit special, as there is no synchronous * variant, so we MUST create an event flag to synchronise on. */ if ((status = lib$get_ef(&efn)) != SS$_NORMAL) { lib$signal(status); return 0; } if ((status = sys$getrmi(efn, 0, 0, RMI_items_64bit, &iosb, 0, 0)) != SS$_NORMAL) { lib$signal(status); return 0; } if ((status = sys$synch(efn, &iosb)) != SS$_NORMAL) { lib$signal(status); return 0; } if (iosb.iosb$l_getxxi_status != SS$_NORMAL) { lib$signal(iosb.iosb$l_getxxi_status); return 0; } if ((status = sys$getrmi(efn, 0, 0, RMI_items, &iosb, 0, 0)) != SS$_NORMAL) { lib$signal(status); return 0; } if ((status = sys$synch(efn, &iosb)) != SS$_NORMAL) { lib$signal(status); return 0; } if (iosb.iosb$l_getxxi_status != SS$_NORMAL) { lib$signal(iosb.iosb$l_getxxi_status); return 0; } if ((status = lib$free_ef(&efn)) != SS$_NORMAL) { lib$signal(status); return 0; } } massage_JPI(JPI_items); /* * If we can't feed the requirements from the caller, we're in deep trouble. */ if (!ossl_assert(total_length >= bytes_needed)) { char buf[100]; /* That should be enough */ BIO_snprintf(buf, sizeof(buf), "Needed: %zu, Available: %zu", bytes_needed, total_length); RANDerr(RAND_F_DATA_COLLECT_METHOD, RAND_R_RANDOM_POOL_UNDERFLOW); ERR_add_error_data(1, buf); return 0; } /* * Try not to overfeed the pool */ if (total_length > bytes_remaining) total_length = bytes_remaining; /* We give the pessimistic value for the amount of entropy */ rand_pool_add(pool, (unsigned char *)data.buffer, total_length, 8 * total_length / ENTROPY_FACTOR); return rand_pool_entropy_available(pool); } /* * SYS$GET_ENTROPY METHOD * ====================== * * This is a high entropy method based on a new system service that is * based on getentropy() from FreeBSD 12. It's only used if available, * and its availability is detected at run-time. * * We assume that this function provides full entropy random output. */ #define PUBLIC_VECTORS "SYS$LIBRARY:SYS$PUBLIC_VECTORS.EXE" #define GET_ENTROPY "SYS$GET_ENTROPY" static int get_entropy_address_flag = 0; static int (*get_entropy_address)(void *buffer, size_t buffer_size) = NULL; static int init_get_entropy_address(void) { if (get_entropy_address_flag == 0) get_entropy_address = dlsym(dlopen(PUBLIC_VECTORS, 0), GET_ENTROPY); get_entropy_address_flag = 1; return get_entropy_address != NULL; } size_t get_entropy_method(RAND_POOL *pool) { /* * The documentation says that SYS$GET_ENTROPY will give a maximum of * 256 bytes of data. */ unsigned char buffer[256]; size_t bytes_needed; size_t bytes_to_get = 0; uint32_t status; for (bytes_needed = rand_pool_bytes_needed(pool, 1); bytes_needed > 0; bytes_needed -= bytes_to_get) { bytes_to_get = bytes_needed > sizeof(buffer) ? sizeof(buffer) : bytes_needed; status = get_entropy_address(buffer, bytes_to_get); if (status == SS$_RETRY) { /* Set to zero so the loop doesn't diminish |bytes_needed| */ bytes_to_get = 0; /* Should sleep some amount of time */ continue; } if (status != SS$_NORMAL) { lib$signal(status); return 0; } rand_pool_add(pool, buffer, bytes_to_get, 8 * bytes_to_get); } return rand_pool_entropy_available(pool); } /* * MAIN ENTROPY ACQUISITION FUNCTIONS * ================================== * * These functions are called by the RAND / DRBG functions */ size_t rand_pool_acquire_entropy(RAND_POOL *pool) { if (init_get_entropy_address()) return get_entropy_method(pool); return data_collect_method(pool); } int rand_pool_add_nonce_data(RAND_POOL *pool) { /* * Two variables to ensure that two nonces won't ever be the same */ static unsigned __int64 last_time = 0; static unsigned __int32 last_seq = 0; struct { pid_t pid; CRYPTO_THREAD_ID tid; unsigned __int64 time; unsigned __int32 seq; } data; /* Erase the entire structure including any padding */ memset(&data, 0, sizeof(data)); /* * Add process id, thread id, a timestamp, and a sequence number in case * the same time stamp is repeated, to ensure that the nonce is unique * with high probability for different process instances. * * The normal OpenVMS time is specified to be high granularity (100ns), * but the time update granularity given by sys$gettim() may be lower. * * OpenVMS version 8.4 (which is the latest for Alpha and Itanium) and * on have sys$gettim_prec() as well, which is supposedly having a better * time update granularity, but tests on Itanium (and even Alpha) have * shown that compared with sys$gettim(), the difference is marginal, * so of very little significance in terms of entropy. * Given that, and that it's a high ask to expect everyone to have * upgraded to OpenVMS version 8.4, only sys$gettim() is used, and a * sequence number is added as well, in case sys$gettim() returns the * same time value more than once. * * This function is assumed to be called under thread lock, and does * therefore not take concurrency into account. */ data.pid = getpid(); data.tid = CRYPTO_THREAD_get_current_id(); data.seq = 0; sys$gettim((void*)&data.time); if (data.time == last_time) { data.seq = ++last_seq; } else { last_time = data.time; last_seq = 0; } return rand_pool_add(pool, (unsigned char *)&data, sizeof(data), 0); } int rand_pool_add_additional_data(RAND_POOL *pool) { struct { CRYPTO_THREAD_ID tid; unsigned __int64 time; } data = { 0 }; /* * Add some noise from the thread id and a timer. The thread id adds a * little randomness if the drbg is accessed concurrently (which is the * case for the drbg). */ data.tid = CRYPTO_THREAD_get_current_id(); sys$gettim((void*)&data.time); return rand_pool_add(pool, (unsigned char *)&data, sizeof(data), 0); } int rand_pool_init(void) { return 1; } void rand_pool_cleanup(void) { } void rand_pool_keep_random_devices_open(int keep) { } #endif