• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *
3  * honggfuzz - fuzzing routines
4  * -----------------------------------------
5  *
6  * Authors: Robert Swiecki <swiecki@google.com>
7  *          Felix Gröbert <groebert@google.com>
8  *
9  * Copyright 2010-2018 by Google Inc. All Rights Reserved.
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License"); you may
12  * not use this file except in compliance with the License. You may obtain
13  * a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
20  * implied. See the License for the specific language governing
21  * permissions and limitations under the License.
22  *
23  */
24 
25 #include "fuzz.h"
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <inttypes.h>
30 #include <libgen.h>
31 #include <pthread.h>
32 #include <signal.h>
33 #include <stddef.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/mman.h>
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #include <sys/time.h>
42 #include <sys/types.h>
43 #include <time.h>
44 #include <unistd.h>
45 
46 #include "arch.h"
47 #include "honggfuzz.h"
48 #include "input.h"
49 #include "libhfcommon/common.h"
50 #include "libhfcommon/files.h"
51 #include "libhfcommon/log.h"
52 #include "libhfcommon/util.h"
53 #include "mangle.h"
54 #include "report.h"
55 #include "sanitizers.h"
56 #include "socketfuzzer.h"
57 #include "subproc.h"
58 
59 static time_t termTimeStamp = 0;
60 
fuzz_isTerminating(void)61 bool fuzz_isTerminating(void) {
62     if (ATOMIC_GET(termTimeStamp) != 0) {
63         return true;
64     }
65     return false;
66 }
67 
fuzz_setTerminating(void)68 void fuzz_setTerminating(void) {
69     if (ATOMIC_GET(termTimeStamp) != 0) {
70         return;
71     }
72     ATOMIC_SET(termTimeStamp, time(NULL));
73 }
74 
fuzz_shouldTerminate()75 bool fuzz_shouldTerminate() {
76     if (ATOMIC_GET(termTimeStamp) == 0) {
77         return false;
78     }
79     if ((time(NULL) - ATOMIC_GET(termTimeStamp)) > 5) {
80         return true;
81     }
82     return false;
83 }
84 
fuzz_getState(honggfuzz_t * hfuzz)85 static fuzzState_t fuzz_getState(honggfuzz_t* hfuzz) {
86     return ATOMIC_GET(hfuzz->feedback.state);
87 }
88 
fuzz_writeCovFile(const char * dir,const uint8_t * data,size_t len)89 static bool fuzz_writeCovFile(const char* dir, const uint8_t* data, size_t len) {
90     char fname[PATH_MAX];
91 
92     uint64_t crc64f = util_CRC64(data, len);
93     uint64_t crc64r = util_CRC64Rev(data, len);
94     snprintf(fname, sizeof(fname), "%s/%016" PRIx64 "%016" PRIx64 ".%08" PRIx32 ".honggfuzz.cov",
95         dir, crc64f, crc64r, (uint32_t)len);
96 
97     if (files_exists(fname)) {
98         LOG_D("File '%s' already exists in the output corpus directory '%s'", fname, dir);
99         return true;
100     }
101 
102     LOG_D("Adding file '%s' to the corpus directory '%s'", fname, dir);
103 
104     if (!files_writeBufToFile(fname, data, len, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC)) {
105         LOG_W("Couldn't write buffer to file '%s'", fname);
106         return false;
107     }
108 
109     return true;
110 }
111 
fuzz_addFileToFileQ(honggfuzz_t * hfuzz,const uint8_t * data,size_t len)112 static void fuzz_addFileToFileQ(honggfuzz_t* hfuzz, const uint8_t* data, size_t len) {
113     ATOMIC_SET(hfuzz->timing.lastCovUpdate, time(NULL));
114 
115     struct dynfile_t* dynfile = (struct dynfile_t*)util_Malloc(sizeof(struct dynfile_t));
116     dynfile->size = len;
117     dynfile->data = (uint8_t*)util_Malloc(len);
118     memcpy(dynfile->data, data, len);
119 
120     MX_SCOPED_RWLOCK_WRITE(&hfuzz->io.dynfileq_mutex);
121     TAILQ_INSERT_TAIL(&hfuzz->io.dynfileq, dynfile, pointers);
122     hfuzz->io.dynfileqCnt++;
123 
124     if (hfuzz->socketFuzzer.enabled) {
125         /* Don't add coverage data to files in socketFuzzer mode */
126         return;
127     }
128 
129     if (!fuzz_writeCovFile(hfuzz->io.covDirAll, data, len)) {
130         LOG_E("Couldn't save the coverage data to '%s'", hfuzz->io.covDirAll);
131     }
132 
133     /* No need to add files to the new coverage dir, if this is just the dry-run phase */
134     if (fuzz_getState(hfuzz) == _HF_STATE_DYNAMIC_DRY_RUN || hfuzz->io.covDirNew == NULL) {
135         return;
136     }
137 
138     if (!fuzz_writeCovFile(hfuzz->io.covDirNew, data, len)) {
139         LOG_E("Couldn't save the new coverage data to '%s'", hfuzz->io.covDirNew);
140     }
141 }
142 
fuzz_setDynamicMainState(run_t * run)143 static void fuzz_setDynamicMainState(run_t* run) {
144     /* All threads need to indicate willingness to switch to the DYNAMIC_MAIN state. Count them! */
145     static uint32_t cnt = 0;
146     ATOMIC_PRE_INC(cnt);
147 
148     static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
149     MX_SCOPED_LOCK(&state_mutex);
150 
151     if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MAIN) {
152         return;
153     }
154 
155     LOG_I("Entering phase 2/3: Switching to Dynamic Main (Feedback Driven Mode)");
156     ATOMIC_SET(run->global->feedback.state, _HF_STATE_DYNAMIC_SWITCH_TO_MAIN);
157 
158     for (;;) {
159         /* Check if all threads have already reported in for changing state */
160         if (ATOMIC_GET(cnt) == run->global->threads.threadsMax) {
161             break;
162         }
163         if (fuzz_isTerminating()) {
164             return;
165         }
166         util_sleepForMSec(10); /* Check every 10ms */
167     }
168 
169     LOG_I("Entering phase 3/3: Dynamic Main (Feedback Driven Mode)");
170     snprintf(run->origFileName, sizeof(run->origFileName), "[DYNAMIC]");
171     ATOMIC_SET(run->global->feedback.state, _HF_STATE_DYNAMIC_MAIN);
172 
173     /*
174      * If the initial fuzzing yielded no useful coverage, just add a single 1-byte file to the
175      * dynamic corpus, so the dynamic phase doesn't fail because of lack of useful inputs
176      */
177     if (run->global->io.dynfileqCnt == 0) {
178         const char* single_byte = run->global->cfg.only_printable ? " " : "\0";
179         fuzz_addFileToFileQ(run->global, (const uint8_t*)single_byte, 1U);
180     }
181 }
182 
fuzz_perfFeedback(run_t * run)183 static void fuzz_perfFeedback(run_t* run) {
184     if (run->global->feedback.skipFeedbackOnTimeout && run->tmOutSignaled) {
185         return;
186     }
187 
188     LOG_D("New file size: %zu, Perf feedback new/cur (instr,branch): %" PRIu64 "/%" PRIu64
189           "/%" PRIu64 "/%" PRIu64 ", BBcnt new/total: %" PRIu64 "/%" PRIu64,
190         run->dynamicFileSz, run->linux.hwCnts.cpuInstrCnt, run->global->linux.hwCnts.cpuInstrCnt,
191         run->linux.hwCnts.cpuBranchCnt, run->global->linux.hwCnts.cpuBranchCnt,
192         run->linux.hwCnts.newBBCnt, run->global->linux.hwCnts.bbCnt);
193 
194     MX_SCOPED_LOCK(&run->global->feedback.feedback_mutex);
195 
196     uint64_t softCntPc = 0;
197     uint64_t softCntEdge = 0;
198     uint64_t softCntCmp = 0;
199     if (run->global->feedback.bbFd != -1) {
200         softCntPc = ATOMIC_GET(run->global->feedback.feedbackMap->pidFeedbackPc[run->fuzzNo]);
201         ATOMIC_CLEAR(run->global->feedback.feedbackMap->pidFeedbackPc[run->fuzzNo]);
202         softCntEdge = ATOMIC_GET(run->global->feedback.feedbackMap->pidFeedbackEdge[run->fuzzNo]);
203         ATOMIC_CLEAR(run->global->feedback.feedbackMap->pidFeedbackEdge[run->fuzzNo]);
204         softCntCmp = ATOMIC_GET(run->global->feedback.feedbackMap->pidFeedbackCmp[run->fuzzNo]);
205         ATOMIC_CLEAR(run->global->feedback.feedbackMap->pidFeedbackCmp[run->fuzzNo]);
206     }
207 
208     int64_t diff0 = run->global->linux.hwCnts.cpuInstrCnt - run->linux.hwCnts.cpuInstrCnt;
209     int64_t diff1 = run->global->linux.hwCnts.cpuBranchCnt - run->linux.hwCnts.cpuBranchCnt;
210 
211     /* Any increase in coverage (edge, pc, cmp, hw) counters forces adding input to the corpus */
212     if (run->linux.hwCnts.newBBCnt > 0 || softCntPc > 0 || softCntEdge > 0 || softCntCmp > 0 ||
213         diff0 < 0 || diff1 < 0) {
214         if (diff0 < 0) {
215             run->global->linux.hwCnts.cpuInstrCnt = run->linux.hwCnts.cpuInstrCnt;
216         }
217         if (diff1 < 0) {
218             run->global->linux.hwCnts.cpuBranchCnt = run->linux.hwCnts.cpuBranchCnt;
219         }
220         run->global->linux.hwCnts.bbCnt += run->linux.hwCnts.newBBCnt;
221         run->global->linux.hwCnts.softCntPc += softCntPc;
222         run->global->linux.hwCnts.softCntEdge += softCntEdge;
223         run->global->linux.hwCnts.softCntCmp += softCntCmp;
224 
225         LOG_I("Size:%zu (i,b,hw,edge,ip,cmp): %" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64
226               "/%" PRIu64 "/%" PRIu64 ", Tot:%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64
227               "/%" PRIu64 "/%" PRIu64,
228             run->dynamicFileSz, run->linux.hwCnts.cpuInstrCnt, run->linux.hwCnts.cpuBranchCnt,
229             run->linux.hwCnts.newBBCnt, softCntEdge, softCntPc, softCntCmp,
230             run->global->linux.hwCnts.cpuInstrCnt, run->global->linux.hwCnts.cpuBranchCnt,
231             run->global->linux.hwCnts.bbCnt, run->global->linux.hwCnts.softCntEdge,
232             run->global->linux.hwCnts.softCntPc, run->global->linux.hwCnts.softCntCmp);
233 
234         fuzz_addFileToFileQ(run->global, run->dynamicFile, run->dynamicFileSz);
235 
236         if (run->global->socketFuzzer.enabled) {
237             LOG_D("SocketFuzzer: fuzz: new BB (perf)");
238             fuzz_notifySocketFuzzerNewCov(run->global);
239         }
240     }
241 }
242 
243 /* Return value indicates whether report file should be updated with the current verified crash */
fuzz_runVerifier(run_t * run)244 static bool fuzz_runVerifier(run_t* run) {
245     if (!run->crashFileName[0] || !run->backtrace) {
246         return false;
247     }
248 
249     uint64_t backtrace = run->backtrace;
250 
251     char origCrashPath[PATH_MAX];
252     snprintf(origCrashPath, sizeof(origCrashPath), "%s", run->crashFileName);
253     /* Workspace is inherited, just append a extra suffix */
254     char verFile[PATH_MAX];
255     snprintf(verFile, sizeof(verFile), "%s.verified", origCrashPath);
256 
257     if (files_exists(verFile)) {
258         LOG_D("Crash file to verify '%s' is already verified as '%s'", origCrashPath, verFile);
259         return false;
260     }
261 
262     for (int i = 0; i < _HF_VERIFIER_ITER; i++) {
263         LOG_I("Launching verifier for HASH: %" PRIx64 " (iteration: %d out of %d)", run->backtrace,
264             i + 1, _HF_VERIFIER_ITER);
265         run->timeStartedMillis = 0;
266         run->backtrace = 0;
267         run->access = 0;
268         run->exception = 0;
269         run->mainWorker = false;
270 
271         if (!subproc_Run(run)) {
272             LOG_F("subproc_Run()");
273         }
274 
275         /* If stack hash doesn't match skip name tag and exit */
276         if (run->backtrace != backtrace) {
277             LOG_E("Verifier stack mismatch: (original) %" PRIx64 " != (new) %" PRIx64, backtrace,
278                 run->backtrace);
279             run->backtrace = backtrace;
280             return true;
281         }
282 
283         LOG_I("Verifier for HASH: %" PRIx64 " (iteration: %d, left: %d). MATCH!", run->backtrace,
284             i + 1, _HF_VERIFIER_ITER - i - 1);
285     }
286 
287     /* Copy file with new suffix & remove original copy */
288     int fd = TEMP_FAILURE_RETRY(open(verFile, O_CREAT | O_EXCL | O_WRONLY, 0600));
289     if (fd == -1 && errno == EEXIST) {
290         LOG_I("It seems that '%s' already exists, skipping", verFile);
291         return false;
292     }
293     if (fd == -1) {
294         PLOG_E("Couldn't create '%s'", verFile);
295         return true;
296     }
297     defer {
298         close(fd);
299     };
300     if (!files_writeToFd(fd, run->dynamicFile, run->dynamicFileSz)) {
301         LOG_E("Couldn't save verified file as '%s'", verFile);
302         unlink(verFile);
303         return true;
304     }
305 
306     LOG_I("Verified crash for HASH: %" PRIx64 " and saved it as '%s'", backtrace, verFile);
307     ATOMIC_PRE_INC(run->global->cnts.verifiedCrashesCnt);
308 
309     return true;
310 }
311 
fuzz_fetchInput(run_t * run)312 static bool fuzz_fetchInput(run_t* run) {
313     {
314         fuzzState_t st = fuzz_getState(run->global);
315         if (st == _HF_STATE_DYNAMIC_DRY_RUN || st == _HF_STATE_DYNAMIC_SWITCH_TO_MAIN) {
316             run->mutationsPerRun = 0U;
317             if (input_prepareStaticFile(run, /* rewind= */ false, true)) {
318                 return true;
319             }
320             fuzz_setDynamicMainState(run);
321             run->mutationsPerRun = run->global->mutate.mutationsPerRun;
322         }
323     }
324 
325     if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MAIN) {
326         if (run->global->exe.externalCommand) {
327             if (!input_prepareExternalFile(run)) {
328                 LOG_E("input_prepareFileExternally() failed");
329                 return false;
330             }
331         } else if (run->global->exe.feedbackMutateCommand) {
332             if (!input_prepareDynamicInput(run, false)) {
333                 LOG_E("input_prepareFileDynamically() failed");
334                 return false;
335             }
336         } else if (!input_prepareDynamicInput(run, true)) {
337             LOG_E("input_prepareFileDynamically() failed");
338             return false;
339         }
340     }
341 
342     if (fuzz_getState(run->global) == _HF_STATE_STATIC) {
343         if (run->global->exe.externalCommand) {
344             if (!input_prepareExternalFile(run)) {
345                 LOG_E("input_prepareFileExternally() failed");
346                 return false;
347             }
348         } else if (run->global->exe.feedbackMutateCommand) {
349             if (!input_prepareStaticFile(run, true, false)) {
350                 LOG_E("input_prepareFileDynamically() failed");
351                 return false;
352             }
353         } else if (!input_prepareStaticFile(run, true /* rewind */, true)) {
354             LOG_E("input_prepareFile() failed");
355             return false;
356         }
357     }
358 
359     if (run->global->exe.postExternalCommand && !input_postProcessFile(run)) {
360         LOG_E("input_postProcessFile() failed");
361         return false;
362     }
363 
364     if (run->global->exe.feedbackMutateCommand && !input_feedbackMutateFile(run)) {
365         LOG_E("input_feedbackMutateFile() failed");
366         return false;
367     }
368 
369     return true;
370 }
371 
fuzz_fuzzLoop(run_t * run)372 static void fuzz_fuzzLoop(run_t* run) {
373     run->timeStartedMillis = 0;
374     run->crashFileName[0] = '\0';
375     run->pc = 0;
376     run->backtrace = 0;
377     run->access = 0;
378     run->exception = 0;
379     run->report[0] = '\0';
380     run->mainWorker = true;
381     run->mutationsPerRun = run->global->mutate.mutationsPerRun;
382     run->dynamicFileSz = 0;
383     run->dynamicFileCopyFd = -1;
384     run->tmOutSignaled = false;
385 
386     run->linux.hwCnts.cpuInstrCnt = 0;
387     run->linux.hwCnts.cpuBranchCnt = 0;
388     run->linux.hwCnts.bbCnt = 0;
389     run->linux.hwCnts.newBBCnt = 0;
390 
391     if (!fuzz_fetchInput(run)) {
392         LOG_F("Cound't prepare input for fuzzing");
393     }
394     if (!subproc_Run(run)) {
395         LOG_F("Couldn't run fuzzed command");
396     }
397 
398     if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
399         fuzz_perfFeedback(run);
400     }
401     if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) {
402         return;
403     }
404     report_Report(run);
405 }
406 
fuzz_fuzzLoopSocket(run_t * run)407 static void fuzz_fuzzLoopSocket(run_t* run) {
408     run->pid = 0;
409     run->timeStartedMillis = 0;
410     run->crashFileName[0] = '\0';
411     run->pc = 0;
412     run->backtrace = 0;
413     run->access = 0;
414     run->exception = 0;
415     run->report[0] = '\0';
416     run->mainWorker = true;
417     run->mutationsPerRun = run->global->mutate.mutationsPerRun;
418     run->dynamicFileSz = 0;
419     run->dynamicFileCopyFd = -1;
420     run->tmOutSignaled = false;
421 
422     run->linux.hwCnts.cpuInstrCnt = 0;
423     run->linux.hwCnts.cpuBranchCnt = 0;
424     run->linux.hwCnts.bbCnt = 0;
425     run->linux.hwCnts.newBBCnt = 0;
426 
427     LOG_I("------------------------------------------------------");
428 
429     /* First iteration: Start target
430        Other iterations: re-start target, if necessary
431        subproc_Run() will decide by itself if a restart is necessary, via
432        subproc_New()
433     */
434     LOG_D("------[ 1: subproc_run");
435     if (!subproc_Run(run)) {
436         LOG_W("Couldn't run server");
437     }
438 
439     /* Tell the external fuzzer to send data to target
440        The fuzzer will notify us when finished; block until then.
441     */
442     LOG_D("------[ 2: fetch input");
443     if (!fuzz_waitForExternalInput(run)) {
444         /* Fuzzer could not connect to target, and told us to
445            restart it. Do it on the next iteration. */
446         LOG_D("------[ 2.1: Target down, will restart it");
447         return;
448     }
449 
450     LOG_D("------[ 3: feedback");
451     if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
452         fuzz_perfFeedback(run);
453     }
454     if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) {
455         return;
456     }
457 
458     report_Report(run);
459 }
460 
fuzz_threadNew(void * arg)461 static void* fuzz_threadNew(void* arg) {
462     honggfuzz_t* hfuzz = (honggfuzz_t*)arg;
463     unsigned int fuzzNo = ATOMIC_POST_INC(hfuzz->threads.threadsActiveCnt);
464     LOG_I("Launched new fuzzing thread, no. #%" PRId32, fuzzNo);
465 
466     run_t run = {
467         .global = hfuzz,
468         .pid = 0,
469         .dynfileqCurrent = NULL,
470         .dynamicFile = NULL,
471         .dynamicFileFd = -1,
472         .fuzzNo = fuzzNo,
473         .persistentSock = -1,
474         .tmOutSignaled = false,
475         .origFileName = "[DYNAMIC]",
476     };
477 
478     /* Do not try to handle input files with socketfuzzer */
479     if (!hfuzz->socketFuzzer.enabled) {
480         if (!(run.dynamicFile = files_mapSharedMem(hfuzz->mutate.maxFileSz, &run.dynamicFileFd,
481                   "hfuzz-input", run.global->io.workDir))) {
482             LOG_F("Couldn't create an input file of size: %zu", hfuzz->mutate.maxFileSz);
483         }
484     }
485     defer {
486         if (run.dynamicFileFd != -1) {
487             close(run.dynamicFileFd);
488         }
489     };
490 
491     if (!arch_archThreadInit(&run)) {
492         LOG_F("Could not initialize the thread");
493     }
494 
495     for (;;) {
496         /* Check if dry run mode with verifier enabled */
497         if (run.global->mutate.mutationsPerRun == 0U && run.global->cfg.useVerifier &&
498             !hfuzz->socketFuzzer.enabled) {
499             if (ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >= run.global->io.fileCnt) {
500                 break;
501             }
502         }
503         /* Check for max iterations limit if set */
504         else if ((ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >=
505                      run.global->mutate.mutationsMax) &&
506                  run.global->mutate.mutationsMax) {
507             break;
508         }
509 
510         input_setSize(&run, run.global->mutate.maxFileSz);
511         if (hfuzz->socketFuzzer.enabled) {
512             fuzz_fuzzLoopSocket(&run);
513         } else {
514             fuzz_fuzzLoop(&run);
515         }
516 
517         if (fuzz_isTerminating()) {
518             break;
519         }
520 
521         if (run.global->cfg.exitUponCrash && ATOMIC_GET(run.global->cnts.crashesCnt) > 0) {
522             LOG_I("Seen a crash. Terminating all fuzzing threads");
523             fuzz_setTerminating();
524             break;
525         }
526     }
527 
528     if (run.pid) {
529         kill(run.pid, SIGKILL);
530     }
531 
532     LOG_I("Terminating thread no. #%" PRId32 ", left: %zu", fuzzNo,
533         hfuzz->threads.threadsMax - ATOMIC_GET(run.global->threads.threadsFinished));
534     ATOMIC_POST_INC(run.global->threads.threadsFinished);
535     return NULL;
536 }
537 
fuzz_threadsStart(honggfuzz_t * hfuzz)538 void fuzz_threadsStart(honggfuzz_t* hfuzz) {
539     if (!arch_archInit(hfuzz)) {
540         LOG_F("Couldn't prepare arch for fuzzing");
541     }
542     if (!sanitizers_Init(hfuzz)) {
543         LOG_F("Couldn't prepare sanitizer options");
544     }
545 
546     if (hfuzz->socketFuzzer.enabled) {
547         /* Don't do dry run with socketFuzzer */
548         LOG_I("Entering phase - Feedback Driven Mode (SocketFuzzer)");
549         hfuzz->feedback.state = _HF_STATE_DYNAMIC_MAIN;
550     } else if (hfuzz->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
551         LOG_I("Entering phase 1/3: Dry Run");
552         hfuzz->feedback.state = _HF_STATE_DYNAMIC_DRY_RUN;
553     } else {
554         LOG_I("Entering phase: Static");
555         hfuzz->feedback.state = _HF_STATE_STATIC;
556     }
557 
558     for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) {
559         if (!subproc_runThread(
560                 hfuzz, &hfuzz->threads.threads[i], fuzz_threadNew, /* joinable= */ true)) {
561             PLOG_F("Couldn't run a thread #%zu", i);
562         }
563     }
564 }
565