/** ** Simple entropy harvester based upon the havege RNG ** ** Copyright 2018-2022 Jirka Hladky hladky DOT jiri AT gmail DOT com ** Copyright 2009-2014 Gary Wuertz gary@issiweb.com ** Copyright 2011-2012 BenEleventh Consulting manolson@beneleventh.com ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . ** */ /** * This compile unit implements the havege algorithm as an inteface to * either a single collector in the calling process or an interface to * multiple collector processes (experimental). */ #include "config.h" #include #include #include #include #include #include "havegetest.h" #include "havegetune.h" /** * The library version interface results in a pair of version definitions * which must agree yet must also be string literals. No foolproof build * mechanism could be devised to ensure this, so a run-time check was added * instead - if the two definitions do not agree, the interface is diabled. */ #define INTERFACE_DISABLED() strcmp(PACKAGE_VERSION,HAVEGE_PREP_VERSION) #if NUMBER_CORES>1 #include #include #include #include #include /** * Collection thread directory */ typedef struct { pid_t main; /* the main thread */ H_UINT exit_ct; /* shutdown counter */ H_UINT count; /* associated count */ H_UINT last; /* last output */ H_UINT *out; /* buffer pointer */ H_UINT fatal; /* fatal error in last */ sem_t flags[1]; /* thread signals */ } H_THREAD; /** * Local prototypes */ static int havege_exit(H_PTR h_ptr); static void havege_ipc(H_PTR h_ptr, H_UINT n, H_UINT sz); static int havege_rngChild(H_PTR h_ptr, H_UINT cNumber); static void havege_unipc(H_PTR h_ptr); #endif /** * Main allocation */ #ifdef ONLINE_TESTS_ENABLE typedef struct { struct h_anchor info; /* Application anchor */ HOST_CFG cfg; /* Runtime environment */ procShared std; /* Shared test data */ } H_SETUP; static int testsConfigure(H_UINT *tot, H_UINT *run, char *options); static void testsStatus(procShared *tps, char *tot, char *prod); static void testReport(H_COLLECT * h_ctxt, H_UINT action, H_UINT prod, H_UINT state, H_UINT ct); static void testReportA(H_PTR h, procA *context); static void testReportB(H_PTR h, procB *context); #else typedef struct { struct h_anchor info; /* Application anchor */ HOST_CFG cfg; /* Runtime environment */ } H_SETUP; #endif /** * Local prototypes */ static void havege_mute(const char *format, ...); /** * Initialize the environment based upon the tuning survey. This includes, * allocation the output buffer (in shared memory if mult-threaded) and * fitting the collection code to the tuning inputs. */ H_PTR havege_create( /* RETURN: app state */ H_PARAMS *params) /* IN: input params */ { H_SETUP *anchor; HOST_CFG *env; H_PTR h = 0; H_UINT n = params->nCores; H_UINT sz = params->ioSz; if (INTERFACE_DISABLED()) return NULL; if (0 == n) n = 1; if (0 == sz) sz = DEFAULT_BUFSZ; anchor = (H_SETUP *)calloc(1, sizeof(H_SETUP)); if (NULL==anchor) return h; h = &anchor->info; h->print_msg = params->msg_out==0? havege_mute : params->msg_out; h->metering = params->metering; env = &anchor->cfg; havege_tune(env, params); h->error = H_NOERR; h->arch = ARCH; h->inject = params->injection; h->n_cores = n; h->havege_opts = params->options; h->i_collectSz = params->collectSize==0? NDSIZECOLLECT : params->collectSize; h->i_readSz = sz; h->tuneData = env; h->cpu = &env->cpus[env->a_cpu]; h->instCache = &env->caches[env->i_tune]; h->dataCache = &env->caches[env->d_tune]; #ifdef ONLINE_TESTS_ENABLE { static const H_UINT tests[5] = {B_RUN, A_RUN}; H_UINT tot=0,run=0; H_UINT i, j; procShared *tps = (procShared *)&anchor->std; if (testsConfigure(&tot, &run, params->testSpec)) { h->error = H_NOTESTSPEC; return h; } for(i=j=0;i<2;i++) if (0!=(tot & tests[i])) { tps->testsUsed |= tests[i]; tps->totTests[j].action = tests[i]; tps->totTests[j++].options = tot; } for(i=j=0;i<2;i++) if (0!=(run & tests[i])) { tps->testsUsed |= tests[i]; tps->runTests[j].action = tests[i]; tps->runTests[j++].options = run; } testsStatus(tps, tps->totText, tps->prodText); tps->report = testReport; h->testData = tps; if (havege_test(tps, params)) { h->error = H_NOTESTMEM; return h; } } #endif #if NUMBER_CORES>1 havege_ipc(h, n, sz); #else h->io_buf = malloc(sz); h->threads = NULL; #endif if (NULL==h->io_buf) { h->error = H_NOBUF; return h; } havege_ndsetup(h); return h; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" void havege_reparent( H_PTR hptr) { #if NUMBER_CORES>1 H_THREAD *t = (H_THREAD *)hptr->threads; if (0 == t) return; /* single-threaded */ t->main = getpid(); #endif } #pragma GCC diagnostic pop /** * Destructor. In a multi-collector build, this method should be called from a signal handler * to avoid creating processes. */ void havege_destroy( /* RETURN: none */ H_PTR hptr) /* IN-OUT: app anchor */ { if (NULL != hptr) { H_COLLECT *htemp; void *temp; #if NUMBER_CORES>1 if (!havege_exit(hptr)) return; /* only main thread continues */ #endif if (0 != (temp=hptr->io_buf)) { hptr->io_buf = 0; free(temp); } #ifdef ONLINE_TESTS_ENABLE if (0 != (temp=hptr->testData)) { double *g = ((procShared *)temp)->G; hptr->testData = 0; if (0 != g) free(g); } #endif if (0 != (htemp=hptr->collector)) { hptr->collector = 0; havege_nddestroy(htemp); } free(hptr); } } /** * Read random words. In the single-collector case, input is read by the calling * the collection method directly. In the multi-collector case, the request info is * signaled to the collector last read and the caller waits for a completion signal. */ int havege_rng( /* RETURN: number words read */ H_PTR h, /* IN-OUT: app state */ H_UINT *buffer, /* OUT: read buffer */ H_UINT sz) /* IN: number words to read */ { #if NUMBER_CORES>1 H_THREAD *t = (H_THREAD *) h->threads; t->count = sz; t->out = buffer; if (0!=sem_post(&t->flags[t->last])) h->error = H_NORQST; else if (0!=sem_wait(&t->flags[h->n_cores])) h->error = H_NOCOMP; else if (H_NOERR != t->fatal) h->error = t->fatal; #else H_UINT i; for(i=0;icollector); h->error = ((H_COLLECT *)h->collector)->havege_err; #endif return h->error==(H_UINT)H_NOERR? (int) sz : -1; } /** * Start the entropy collector. */ int havege_run( /* RETURN: NZ on failure */ H_PTR h) /* IN-OUT: app anchor */ { int i = 0; #if NUMBER_CORES>1 for(i = 0; i < h->n_cores;i++) if (0 == havege_rngChild(h, i)) return 1; #else if (NULL==(h->collector = havege_ndcreate(h, i))) return 1; #endif return 0; } /** * Report concealed setup data */ void havege_status( /* RETURN: none */ H_PTR h_ptr, /* IN: app state */ H_STATUS h_sts) /* OUT: app state */ { if (0 != h_sts) { HOST_CFG *en = (HOST_CFG *) (h_ptr->tuneData); CACHE_INST *cd = (CACHE_INST *)(h_ptr->dataCache); CACHE_INST *ci = (CACHE_INST *)(h_ptr->instCache); CPU_INST *cp = (CPU_INST *) (h_ptr->cpu); procShared *ps = (procShared *)(h_ptr->testData); h_sts->version = HAVEGE_PREP_VERSION; h_sts->buildOptions = en->buildOpts; h_sts->cpuSources = en->cpuOpts; h_sts->i_cacheSources = en->icacheOpts; h_sts->d_cacheSources = en->dcacheOpts; h_sts->vendor = cp->vendor; h_sts->d_cache = cd->size; h_sts->i_cache = ci->size; h_sts->tot_tests = (0 != ps)? ps->totText :""; h_sts->prod_tests = (0 != ps)? ps->prodText :""; if (0 != ps) { memcpy(h_sts->n_tests, ps->meters, (H_OLT_PROD_B_P+1) * sizeof(H_UINT)); h_sts->last_test8 = ps->lastCoron; } } } /** * Standard status presetations */ int havege_status_dump( /* RETURN: output length */ H_PTR hptr, /* IN: app state */ H_SD_TOPIC topic, /* IN: presentation topic */ char *buf, /* OUT: output area */ size_t len) /* IN: size of buf */ { struct h_status status; int n = 0; if (buf != 0) { *buf = 0; len -= 1; havege_status(hptr, &status); switch(topic) { case H_SD_TOPIC_BUILD: n += snprintf(buf, len, "ver: %s; arch: %s; vend: %s; build: (%s); collect: %uK", status.version, hptr->arch, status.vendor, status.buildOptions, hptr->i_collectSz/1024 ); break; case H_SD_TOPIC_TUNE: n += snprintf(buf, len, "cpu: (%s); data: %uK (%s); inst: %uK (%s); idx: %u/%u; sz: %u/%u", status.cpuSources, status.d_cache, status.d_cacheSources, status.i_cache, status.i_cacheSources, hptr->i_maxidx - hptr->i_idx, hptr->i_maxidx, hptr->i_sz, hptr->i_maxsz ); break; case H_SD_TOPIC_TEST: { H_UINT m; if (strlen(status.tot_tests)>0) { n += snprintf(buf+n, len-n, "tot tests(%s): ", status.tot_tests); if ((m = status.n_tests[ H_OLT_TOT_A_P] + status.n_tests[ H_OLT_TOT_A_F])>0) n += snprintf(buf+n, len-n, "A:%u/%u ", status.n_tests[ H_OLT_TOT_A_P], m); if ((m = status.n_tests[ H_OLT_TOT_B_P] + status.n_tests[ H_OLT_TOT_B_F])>0) n += snprintf(buf+n, len, "B:%u/%u ", status.n_tests[ H_OLT_TOT_B_P], m); } if (strlen(status.prod_tests)>0) { n += snprintf(buf+n, len-n, "continuous tests(%s): ", status.prod_tests); if ((m = status.n_tests[ H_OLT_PROD_A_P] + status.n_tests[ H_OLT_PROD_A_F])>0) n += snprintf(buf+n, len-n, "A:%u/%u ", status.n_tests[ H_OLT_PROD_A_P], m); if ((m = status.n_tests[ H_OLT_PROD_B_P] + status.n_tests[ H_OLT_PROD_B_F])>0) n += snprintf(buf+n, len, "B:%u/%u ", status.n_tests[ H_OLT_PROD_B_P], m); } if (n>0) n += snprintf(buf+n, len-n, " last entropy estimate %g", status.last_test8); } break; case H_SD_TOPIC_SUM: { char units[] = {'T', 'G', 'M', 'K', 0}; double factor[2]; factor[0] = 1024.0 * 1024.0 * 1024.0 * 1024.0; factor[1] = factor[0]; double sz = ((double)hptr->n_fills * hptr->i_collectSz) * sizeof(H_UINT); double ent = ((double) hptr->n_entropy_bytes); int i[2]; for (i[0]=0;0 != units[i[0]];i[0]++) { if (sz >= factor[0]) break; factor[0] /= 1024.0; } for (i[1]=0;0 != units[i[1]];i[1]++) { if (ent >= factor[1]) break; factor[1] /= 1024.0; } n = snprintf(buf, len, "fills: %u, generated: %.4g %c bytes, RNDADDENTROPY: %.4g %c bytes", hptr->n_fills, sz / factor[0], units[i[0]], ent / factor[1], units[i[1]] ); } break; } } return n; } /** * Return-check library prep version. Calling havege_version() with a NULL version * returns the definition of HAVEGE_PREP_VERSION used to build the library. Calling * with HAVEGE_PREP_VERSION as the version checks if this headers definition is * compatible with that of the library, returning NULL if the input is incompatible * with the library. */ const char *havege_version(const char *version) { if (INTERFACE_DISABLED()) return NULL; /** * Version check academic at the moment, but initial idea is to do a table * lookup on the library version to get a pattern to match against the * input version. */ if (version) { H_UINT l_interface=0, l_revision=0, l_age=0; H_UINT p, p_interface, p_revision, p_patch; #ifdef HAVEGE_LIB_VERSION sscanf(HAVEGE_LIB_VERSION, "%u:%u:%u", &l_interface, &l_revision, &l_age); #endif (void)l_interface;(void)l_revision;(void)l_age;(void)p_patch; /* No check for now */ p = sscanf(version, "%u.%u.%u", &p_interface, &p_revision, &p_patch); if (p!=3 || p_interface != 1 || p_revision != 9) return NULL; } return HAVEGE_PREP_VERSION; } /** * Place holder if output display not provided */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" static void havege_mute( /* RETURN: none */ const char *format, /* IN: printf format */ ...) /* IN: args */ { ; } #pragma GCC diagnostic pop #if NUMBER_CORES > 1 /** * Cleanup collector(s). In a multi-collector environment, need to kill * children to avoid zombies. */ static int havege_exit( /* RETURN: NZ if child */ H_PTR h_ptr) /* IN: app state */ { H_THREAD *t = (H_THREAD *)h_ptr->threads; pid_t p; H_UINT i; if (0 == t) return 0; /* Must be main thread */ t->fatal = H_EXIT; for(i=0;iexit_ct;i++) (void)sem_post(&t->flags[i]); if (getpid() != t->main) return 1; do { /* Wait for children */ p = wait(NULL); } while(p != -1 && errno != ECHILD); for(i=0;iexit_ct;i++) (void)sem_destroy(&t->flags[i]); if (i==h_ptr->n_cores) (void)sem_destroy(&t->flags[i]); havege_unipc(h_ptr); /* unmap memory */ return 0; } /** * Initialize IPC mechanism. This consists of setting up a shared memory area * containing the output buffer and the collection thread directory. */ static void havege_ipc( /* RETURN: None */ H_PTR h, /* IN: app state */ H_UINT n, /* IN: number of threads */ H_UINT sz) /* IN: size of buffer */ { void *m; H_THREAD *t; H_UINT m_sz; int i; if (n > NUMBER_CORES) { h->error = H_NOCORES; return; } m = mmap(NULL, m_sz = sz + sizeof(H_THREAD) + n * sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0); if (m != MAP_FAILED) { h->io_buf = m; h->m_sz = m_sz; t = (H_THREAD *)((char *) m + sz); memset(t, 0, sizeof(H_THREAD)); t->main = getpid(); h->threads = t; for(i=0;i<=n;i++) if(sem_init(&t->flags[i],1,0) < 0) { h->error = H_NOINIT; break; } t->exit_ct = i; } } /** * Child harvester task. If task fails to start H_PTR::error will be set to reason. * If task fails after start, H_THREAD::fatal will be set to the reason and a completion * will be posted to prevent the main thread from hanging waiting for a response. */ static int havege_rngChild(/* RETURN: none */ H_PTR h_ptr, /* IN: app state */ H_UINT cNumber) /* IN: collector index */ { H_COLLECT *h_ctxt = 0; H_THREAD *thds = (H_THREAD *) h_ptr->threads; H_UINT cNext, i, r; int pid; switch(pid=fork()) { case 0: h_ctxt = havege_ndcreate(h_ptr, cNumber); if (NULL != h_ctxt) { cNext = (cNumber + 1) % h_ptr->n_cores; while(1) { if (0!=sem_wait(&thds->flags[cNumber])) { thds->fatal = H_NOWAIT; break; } if (H_NOERR != thds->fatal) { havege_nddestroy(h_ctxt); exit(0); } thds->last = cNumber; r = h_ctxt->havege_szFill - h_ctxt->havege_nptr; if (thds->count < r) r = thds->count; for(i=0;iout[i] = havege_ndread(h_ctxt); thds->fatal = h_ctxt->havege_err; if (0==(thds->count -= i)) { if (0!=sem_post(&thds->flags[h_ptr->n_cores])) { thds->fatal = H_NODONE; break; } continue; } thds->out += i; if (0!=sem_post(&thds->flags[cNext])) { thds->fatal = H_NOPOST; break; } #ifdef HAVE_SCHED_YIELD (void)sched_yield(); #endif (void)havege_ndread(h_ctxt); h_ctxt->havege_nptr = 0; } havege_nddestroy(h_ctxt); } else thds->fatal = h_ptr->error; /* h_ptr is a copy!! */ (void)sem_post(&thds->flags[h_ptr->n_cores]); /* announce death! */ break; case -1: h_ptr->error = H_NOTASK; } return pid; } /** * Unmap memory */ static void havege_unipc( /* RETURN: none */ H_PTR h_ptr) /* IN: app state */ { if (0 != h_ptr->m_sz) { munmap(h_ptr->io_buf, h_ptr->m_sz); h_ptr->io_buf = 0; } } #endif #ifdef ONLINE_TESTS_ENABLE /** * Interpret options string as settings. The option string consists of terms * like "[t|c][a[1-8][w]|b[w]]". */ static int testsConfigure( /* RETURN: non-zero on error */ H_UINT *tot, /* OUT: tot test options */ H_UINT *run, /* OUT: run test options */ char *options) /* IN: option string */ { H_UINT section=0; int c; if (options==0) options = DEFAULT_TEST_OPTIONS; while(0 != (c = *options++)) { switch(c) { case 'T': case 't': /* tot test */ section = 't'; *tot = 0; break; case 'C': case 'c': /* production test */ section = 'c'; *run = 0; break; case 'A': case 'a': if (!section) return 1; c = atoi(options); if (c >= 1 && c < 9) { c = 1<totTests; for(i=0;i<2;i++,p = tps->runTests, dst = prod) { for(j=0;j<2;j++,p++) { switch(p->action) { case A_RUN: *dst++ = 'A'; if (0!=(m = p->options & A_CYCLE)) { for(k=0;m>>=1 != 0;k++); *dst++ = '0' + k; } if (0 != (p->options & A_WARN)) *dst++ = 'w'; break; case B_RUN: *dst++ = 'B'; if (0 != (p->options & B_WARN)) *dst++ = 'w'; break; } *dst = 0; } } } /** * Reporting unit for tests */ static void testReport( H_COLLECT * h_ctxt, /* IN-OUT: collector context */ H_UINT action, /* IN: A_RUN or B_RUN */ H_UINT prod, /* IN: 0==tot, else continuous */ H_UINT state, /* IN: state variable */ H_UINT ct) /* IN: bytes consumed */ { H_PTR h_ptr = (H_PTR)(h_ctxt->havege_app); onlineTests *context = (onlineTests *) h_ctxt->havege_tests; char *result; switch(state) { case TEST_DONE: result = "success"; break; case TEST_RETRY: result = "retry"; break; case TEST_IGNORE: result = "warning"; break; default: result = "failure"; } h_ptr->print_msg("AIS-31 %s procedure %s: %s %d bytes fill %d\n", prod==0? "tot" : "continuous", action==A_RUN? "A":"B", result, ct, h_ptr->n_fills); if (0 != (h_ptr->havege_opts & (H_DEBUG_OLTR|H_DEBUG_OLT))) switch(action){ case A_RUN: testReportA(h_ptr, context->pA); break; case B_RUN: testReportB(h_ptr, context->pB); break; } } /** * Reporting unit for procedure A. Results are 0-257*(1-[4 or 5]) */ static void testReportA( /* RETURN: nothing */ H_PTR h_ptr, /* IN: application instance */ procA *p) /* IN: proc instance */ { static const char * pa_tests[6] = {"test0","test1","test2","test3","test4","test5"}; H_UINT ran[6],sum[6]; H_UINT ct, i, j, k; for (i=0;i<6;i++) ran[i] = sum[i] = 0; for(i=0;itestRun;i++){ ct = p->results[i].testResult; j = ct>>8; ran[j] += 1; if (0==(ct & 0xff)) sum[j] += 1; } h_ptr->print_msg("procedure A: %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d\n", pa_tests[0], sum[0], ran[0], pa_tests[1], sum[1], ran[1], pa_tests[2], sum[2], ran[2], pa_tests[3], sum[3], ran[3], pa_tests[4], sum[4], ran[4], pa_tests[5], sum[5], ran[5] ); for(i=k=0;itestRun;i++){ ct = p->results[i].testResult; j = ct>>8; if (j==1) k+=1; if (0!=(ct & 0xff)) h_ptr->print_msg(" %s[%d] failed with %d\n", pa_tests[j%6],k,p->results[i].finalValue); } } /** * Reporting unit for procedure B. Results are 6a-6b-7a[0]-7a[1]-7b[0]-7b[1]-7b[2]-7b[3]-8 */ static void testReportB( /* RETURN: nothing */ H_PTR h_ptr, /* IN: application instance */ procB *p) /* IN: proc instance */ { static const char * pb_tests[5] = {"test6a","test6b","test7a","test7b","test8"}; H_UINT ct, i, j, ran[5],sum[5]; for (i=0;i<5;i++) { ran[i] = sum[i] = 0; } for(i=0;itestNbr;i++){ ct = p->results[i].testResult; j = ct>>8; ran[j] += 1; if (0==(ct & 0xff)) sum[j] += 1; } h_ptr->print_msg("procedure B: %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d\n", pb_tests[0], sum[0], ran[0], pb_tests[1], sum[1], ran[1], pb_tests[2], sum[2], ran[2], pb_tests[3], sum[3], ran[3], pb_tests[4], sum[4], ran[4] ); for(i=0;i<5;i++) ran[i] = p->testNbr; for(i=0;itestNbr;i++){ ct = p->results[i].testResult; j = ct>>8; if (i < ran[j]) ran[j] = i; if (0!=(ct & 0xff)) h_ptr->print_msg(" %s[%d] failed with %g\n", pb_tests[j],i-ran[j],p->results[i].finalValue); } } #endif