Add new test scenario, where app is killed during policy change.
[platform/core/test/security-tests.git] / src / security-manager-tests / test_cases_prepare_app.cpp
1 /*
2  * Copyright (c) 2016-2020 Samsung Electronics Co., Ltd. All rights reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 #include <poll.h>
18 #include <sys/smack.h>
19 #include <sys/capability.h>
20 #include <sys/prctl.h>
21 #include <sys/eventfd.h>
22
23 #include <cmath>
24 #include <thread>
25 #include <string>
26 #include <memory>
27 #include <mutex>
28 #include <fstream>
29
30 #include <dpl/test/test_runner_child.h>
31 #include <dpl/test/test_runner.h>
32
33 #include <app_install_helper.h>
34 #include <scoped_installer.h>
35 #include <sm_api.h>
36 #include <sm_commons.h>
37 #include <memory.h>
38 #include <tests_common.h>
39 #include <privilege_names.h>
40
41 using namespace SecurityManagerTest;
42 using namespace PrivilegeNames;
43
44 namespace {
45 bool finish = false;
46 const size_t THREADS = 10;
47
48 const std::string APP_TEST_USER = "app_test_user";
49
50 const std::string ACCESS_DENIED_DIR_PATH = "/usr/share/security-manager/dummy";
51 const std::string EXTERNAL_STORAGE_DIR_PATH = "/opt/media";
52 const std::string MEDIA_STORAGE_RW_DIR_PATH = "/opt/usr/media";
53 const std::string MEDIA_STORAGE_RO_DIR_PATH = "/opt/usr/home/app_test_user/media";
54
55 typedef std::unique_ptr<_cap_struct, decltype(&cap_free)> CapPtr;
56
57 std::string thread_errors;
58 std::mutex error_mutex;
59
60 #define THREAD_ASSERT_MSG(test, message)                                       \
61     do                                                                         \
62     {                                                                          \
63         if (!(test))                                                           \
64         {                                                                      \
65             std::ostringstream assertMsg;                                      \
66             assertMsg << #test << " " << __FILE__ << " " << __LINE__ << " " << \
67                          message << std::endl;                                 \
68             std::lock_guard<std::mutex> guard(error_mutex);                    \
69             thread_errors.append(assertMsg.str());                             \
70         }                                                                      \
71     } while (0)
72
73 void threadFn(int i, const std::string &expectedLabel)
74 {
75     if (i % 2 == 0) {
76         // block all signals
77         sigset_t sigset;
78         THREAD_ASSERT_MSG(sigfillset(&sigset) == 0, "sigfillset failed");
79         THREAD_ASSERT_MSG(sigprocmask(SIG_BLOCK, &sigset, NULL) == 0, "sigprocmask failed");
80     }
81
82     while (!finish)
83         usleep(1000);
84
85     char* label;
86     THREAD_ASSERT_MSG(smack_new_label_from_self(&label) > 0, "smack_new_label_from_self failed");
87     CStringPtr labelPtr(label);
88
89     THREAD_ASSERT_MSG(expectedLabel.compare(label) == 0,
90                       "Thread " << i << " has a wrong label: " << label);
91
92     CapPtr expectedCaps(cap_init(), cap_free);
93     THREAD_ASSERT_MSG(expectedCaps, "cap_init() failed");
94     THREAD_ASSERT_MSG(cap_clear(expectedCaps.get()) == 0, "cap_clear() failed");
95
96     CapPtr realCaps(cap_get_proc(), cap_free);
97     THREAD_ASSERT_MSG(realCaps, "cap_get_proc() failed");
98     THREAD_ASSERT_MSG(cap_compare(realCaps.get(), expectedCaps.get()) == 0,
99                       "Thread " << i << " has wrong caps");
100 }
101
102 struct ThreadWrapper
103 {
104
105     ThreadWrapper()
106     {
107     }
108     ~ThreadWrapper()
109     {
110         finish = true;
111         thread.join();
112     }
113
114     void run(int i, const std::string &expectedLabel)
115     {
116         THREAD_ASSERT_MSG(!thread.joinable(), "Thread already started");
117         thread = std::thread(threadFn, i, expectedLabel);
118     }
119
120     std::thread thread;
121 };
122
123 ino_t getFileInode(const std::string &path)
124 {
125     struct stat st;
126     if (stat(path.c_str(), &st) != 0)
127         return 0;
128
129     return st.st_ino;
130 }
131
132 std::string getTextFileContents(const std::string &path)
133 {
134     std::ifstream in(path.c_str());
135     if (in.fail())
136         return std::string();
137     std::stringstream ss;
138     ss << in.rdbuf();
139     return ss.str();
140 }
141
142 bool isPathBound(const std::string &what, const std::string &where, pid_t pid = 1)
143 {
144     std::string mountinfoPath = std::string("/proc/") + std::to_string(pid) + "/mountinfo";
145     std::string mountinfo = getTextFileContents(mountinfoPath);
146     std::string line = what + " " + where;
147
148     return std::string::npos != mountinfo.find(line);
149 }
150
151 } // anonymous namespace
152
153 RUNNER_TEST_GROUP_INIT(SECURITY_MANAGER_PREPARE_APP)
154
155 RUNNER_CHILD_TEST(security_manager_100_synchronize_credentials_test)
156 {
157     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
158     tmpUser.create();
159
160     AppInstallHelper app("app100", tmpUser.getUid());
161     ScopedInstaller appInstall(app);
162     const std::string expectedLabel = app.generateAppLabel();
163
164     pid_t pid = fork();
165     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
166     if (pid == 0) {
167         {
168             RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
169             Api::prepareAppCandidate();
170             ThreadWrapper threads[THREADS];
171
172             for (size_t i = 0; i < THREADS; i++)
173                 threads[i].run(i, expectedLabel);
174
175             Api::prepareApp(app.getAppId().c_str());
176         }
177         RUNNER_ASSERT_MSG(thread_errors.empty(), std::endl << thread_errors);
178         exit(0);
179     } else {
180         waitPid(pid);
181         Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
182     }
183 }
184
185 RUNNER_CHILD_TEST(security_manager_101_create_namespace_test_n)
186 {
187     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
188     tmpUser.create();
189
190     AppInstallHelper app("app100_n", tmpUser.getUid());
191     ScopedInstaller appInstall(app);
192     const std::string expectedLabel = app.generateAppLabel();
193
194     pid_t pid = fork();
195     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
196     if (pid == 0) {
197         {
198             RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
199             ThreadWrapper threads[THREADS];
200
201             for (size_t i = 0; i < THREADS; i++)
202                 threads[i].run(i, expectedLabel);
203
204             Api::prepareAppCandidate(SECURITY_MANAGER_ERROR_INPUT_PARAM);
205         }
206         RUNNER_ASSERT_MSG(!thread_errors.empty(), std::endl << thread_errors);
207         exit(0);
208     } else {
209         waitPid(pid);
210     }
211 }
212
213 RUNNER_CHILD_TEST(security_manager_101_create_namespace_test)
214 {
215     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
216     tmpUser.create();
217
218     AppInstallHelper app("app101", tmpUser.getUid());
219     ScopedInstaller appInstall(app);
220
221     SynchronizationPipe synchPipe;
222     pid_t pid = fork();
223     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
224     if (pid == 0) {
225         synchPipe.claimParentEp();
226         RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
227         Api::prepareAppCandidate();
228         Api::prepareApp(app.getAppId().c_str());
229         synchPipe.post();
230         synchPipe.wait();
231
232         exit(0);
233     } else {
234         synchPipe.claimChildEp();
235         synchPipe.wait();
236
237         std::string appBindPath = std::string("/var/run/user/") + std::to_string(tmpUser.getUid())
238                                   + "/apps/" + app.generateAppLabel() + "/" + std::to_string(pid);
239         std::string appProcPath = std::string("/proc/") + std::to_string(pid) + "/ns/mnt";
240         std::string launcherProcPath = std::string("/proc/") + std::to_string(getpid()) + "/ns/mnt";
241
242         ino_t appBindInode = getFileInode(appBindPath);
243         ino_t appProcInode = getFileInode(appProcPath);
244         ino_t launcherProcInode = getFileInode(launcherProcPath);
245
246         RUNNER_ASSERT_ERRNO_MSG(appBindInode != 0, "get inode failed");
247         RUNNER_ASSERT_ERRNO_MSG(appProcInode != 0, "get inode failed");
248         RUNNER_ASSERT_ERRNO_MSG(launcherProcInode != 0, "get inode failed");
249
250         RUNNER_ASSERT_ERRNO_MSG(launcherProcInode != appProcInode, "create mount namespace failed");
251         RUNNER_ASSERT_ERRNO_MSG(appBindInode == appProcInode, "bind namespace failed");
252
253         synchPipe.post();
254         waitPid(pid);
255         Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
256     }
257 }
258
259 RUNNER_CHILD_TEST(security_manager_102_check_propagation_test)
260 {
261     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
262     tmpUser.create();
263
264     AppInstallHelper app("app102", tmpUser.getUid());
265     ScopedInstaller appInstall(app);
266
267     SynchronizationPipe synchPipe;
268     pid_t pid = fork();
269     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
270     if (pid == 0) {
271         synchPipe.claimParentEp();
272         RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
273         Api::prepareAppCandidate();
274         Api::prepareApp(app.getAppId().c_str());
275         synchPipe.post();
276         synchPipe.wait();
277
278         exit(0);
279     } else {
280         synchPipe.claimChildEp();
281         synchPipe.wait();
282
283         bool result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
284         RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
285         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
286         RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
287         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
288         RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
289
290         result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH);
291         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
292         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH);
293         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
294         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH);
295         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
296
297         synchPipe.post();
298         waitPid(pid);
299         Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
300     }
301 }
302
303 RUNNER_CHILD_TEST(security_manager_103_policy_change_test)
304 {
305     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
306     tmpUser.create();
307
308     AppInstallHelper app("app103", tmpUser.getUid());
309     app.addPrivileges({PRIV_EXTERNALSTORAGE, PRIV_MEDIASTORAGE});
310     ScopedInstaller appInstall(app);
311
312     SynchronizationPipe synchPipe;
313     pid_t pid = fork();
314     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
315     if (pid == 0) {
316         synchPipe.claimParentEp();
317         RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
318         Api::prepareAppCandidate();
319         Api::prepareApp(app.getAppId().c_str());
320         synchPipe.post();
321         synchPipe.wait();
322
323         exit(0);
324     } else {
325         synchPipe.claimChildEp();
326         synchPipe.wait();
327
328         bool result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
329         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
330         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
331         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
332         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
333         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
334
335         PolicyRequest policyRequest;
336         PolicyEntry policyEntry(app.getAppId(), std::to_string(tmpUser.getUid()), PRIV_EXTERNALSTORAGE);
337         policyEntry.setLevel(PolicyEntry::LEVEL_DENY);
338         policyRequest.addEntry(policyEntry);
339
340         policyEntry = PolicyEntry(app.getAppId(), std::to_string(tmpUser.getUid()), PRIV_MEDIASTORAGE);
341         policyEntry.setLevel(PolicyEntry::LEVEL_DENY);
342         policyRequest.addEntry(policyEntry);
343         Api::sendPolicy(policyRequest);
344
345         result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
346         RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
347         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
348         RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
349         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
350         RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
351
352         policyEntry = PolicyEntry(app.getAppId(),  std::to_string(tmpUser.getUid()), PRIV_EXTERNALSTORAGE);
353         policyEntry.setLevel(PolicyEntry::LEVEL_ALLOW);
354         policyRequest.addEntry(policyEntry);
355
356         policyEntry = PolicyEntry(app.getAppId(),  std::to_string(tmpUser.getUid()), PRIV_MEDIASTORAGE);
357         policyEntry.setLevel(PolicyEntry::LEVEL_ALLOW);
358         policyRequest.addEntry(policyEntry);
359         Api::sendPolicy(policyRequest);
360
361         result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
362         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
363         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
364         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
365         result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
366         RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
367
368         synchPipe.post();
369         waitPid(pid);
370         Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
371     }
372 }
373
374 RUNNER_CHILD_TEST(security_manager_104_policy_change_kill_app_test)
375 {
376     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
377     tmpUser.create();
378
379     AppInstallHelper app("app104", tmpUser.getUid());
380     app.addPrivileges({PRIV_EXTERNALSTORAGE, PRIV_MEDIASTORAGE});
381     ScopedInstaller appInstall(app);
382
383     SynchronizationPipe synchPipe;
384     pid_t pid = fork();
385     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
386     if (pid == 0) {
387         synchPipe.claimParentEp();
388         try {
389             RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
390             Api::prepareAppCandidate();
391             Api::prepareApp(app.getAppId());
392         } catch (...) {
393             synchPipe.post();
394             throw;
395         }
396         synchPipe.post();
397         exit(0);
398     } else {
399         synchPipe.claimChildEp();
400         synchPipe.wait();
401
402         PolicyRequest policyRequest;
403         PolicyEntry policyEntry(app.getAppId(), tmpUser.getUidString(), PRIV_EXTERNALSTORAGE);
404         policyEntry.setLevel(PolicyEntry::LEVEL_DENY);
405         policyRequest.addEntry(policyEntry);
406         Api::sendPolicy(policyRequest);
407
408         waitPid(pid);
409         Api::cleanupApp(app.getAppId(), tmpUser.getUid(), pid);
410     }
411 }
412
413 namespace {
414 class Timestamp {
415     uint64_t _;
416     explicit Timestamp(uint64_t ts) : _(ts) {}
417 public:
418     Timestamp operator-(const Timestamp &other) const {
419         RUNNER_ASSERT(_ > other._);
420         return Timestamp(_ - other._);
421     }
422     Timestamp operator+(const Timestamp &other) const {
423         return Timestamp(_ + other._);
424     }
425     bool operator<(const Timestamp &other) const {
426         return _ < other._;
427     }
428     Timestamp() = default;
429     static Timestamp future(uint64_t ns) {
430         timespec ts;
431         const auto res = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
432         RUNNER_ASSERT_ERRNO(!res);
433         return Timestamp(ts.tv_sec * 1000000000ULL + ts.tv_nsec + ns);
434     }
435     static Timestamp now() {
436         return future(0);
437     }
438     template <size_t nLowDigitsToSkip = 3>
439     static void report(Timestamp *ts, size_t n) {
440         std::sort(ts, ts+n);
441         uint64_t sum = 0, mn = -1, mx = 0;
442         long double qsum = 0;
443         for (size_t i = 0; i < n; i++) {
444             const auto t = ts[i]._;
445             sum += t;
446             qsum += decltype(qsum)(t) * t;
447             mn = std::min(mn, t);
448             mx = std::max(mx, t);
449         }
450         uint64_t avg = sum / n;
451         auto qstddev = qsum/n - decltype(qsum)(avg)*avg;
452         const auto out = [](const char *desc, uint64_t t) {
453             char raw[20];
454         char s[20 + 20/3 + 1];
455         size_t j = 0, i = 0;
456         do
457             raw[j++] = '0' + t % 10ULL;
458         while (t /= 10ULL);
459         for (;;) {
460             s[i++] = raw[--j];
461             if (j <= nLowDigitsToSkip)
462                 break;
463             if (!(j % 3))
464                 s[i++] = ' ';
465         }
466         s[i] = '\0';
467         std::cerr << desc << s;
468     };
469     out("min ", mn);
470     out(" max ", mx);
471     out(" avg ", avg);
472     out(" median ", ts[n/2]._);
473     out(" stddev ", std::floor(std::sqrt(qstddev)));
474     std::cerr << '\n';
475     }
476 };
477
478 template <class T, size_t N>
479 constexpr size_t arraySize(T (&)[N]) { return N; }
480 } // namespace
481
482 RUNNER_TEST(security_manager_200_prepare_app_perf)
483 {
484     constexpr int8_t nThreads = 32;
485     constexpr int8_t nConcurrentAppsSamples[] = { 0 /* 1 app w/ nThreads */, 1, 2, 4, 8, 16, 32 };
486     constexpr uint64_t minTotalBenchTime = 60 * 1000ULL*1000*1000; // 60s
487
488     TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
489     tmpUser.create();
490
491     struct App {
492         AppInstallHelper hlp;
493         pid_t pid;
494     };
495
496     std::vector<Timestamp> candidate, prepare, everything;
497
498     std::vector<App> apps;
499     std::vector<ScopedInstaller> appInstalls;
500
501     constexpr auto nAppsMax = nConcurrentAppsSamples[arraySize(nConcurrentAppsSamples) - 1] ?: 1;
502     apps.reserve(nAppsMax);
503     appInstalls.reserve(nAppsMax);
504
505     const auto uid = tmpUser.getUid();
506     for (int i = 0; i < nAppsMax; i++) {
507         apps.emplace_back(App{AppInstallHelper("app200_" + std::to_string(i), uid), 0});
508         auto &hlp = apps.back().hlp;
509         hlp.addPrivileges({PRIV_EXTERNALSTORAGE, PRIV_MEDIASTORAGE, PRIV_CAMERA, PRIV_INTERNET});
510         hlp.createSharedRODir();
511         appInstalls.emplace_back(ScopedInstaller(hlp));
512     }
513
514     for (const auto nConcurrentAppsDesc : nConcurrentAppsSamples) {
515         const auto nConcurrentApps = nConcurrentAppsDesc ?: 1;
516         const auto timeout = Timestamp::future(minTotalBenchTime / arraySize(nConcurrentAppsSamples));
517         do {
518             SynchronizationPipe synchPipe;
519             auto exitEvFd = eventfd(0, 0);
520             RUNNER_ASSERT(exitEvFd >= 0);
521
522             for (int i = 0; i < nConcurrentApps; i++) {
523                 auto &app = apps[i];
524                 const auto pid = fork();
525                 RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
526                 if (pid)
527                     app.pid = pid;
528                 else {
529                     synchPipe.claimChildEp();
530                     RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
531
532                     const auto appId = app.hlp.getAppId();
533
534                     synchPipe.post(); // declare readiness to start measuring
535                     synchPipe.pollForWait(); // wait for parent to signal all kids simultaneously
536
537                     const auto candBeg = Timestamp::now();
538                     Api::prepareAppCandidate();
539                     const auto candEnd = Timestamp::now();
540
541                     if (!nConcurrentAppsDesc) {
542                         for (int i = 0; i < nThreads; i++)
543                             std::thread([]{ for (;;) usleep(1000); }).detach();
544                     }
545
546                     const auto prepBeg = Timestamp::now();
547                     Api::prepareApp(appId);
548                     const auto prepEnd = Timestamp::now();
549
550                     const Timestamp ts[2] = { candEnd-candBeg, prepEnd-prepBeg };
551                     synchPipe.post(ts, sizeof ts); // post measurement payload
552
553                     // stay idle until all kids are done to simulate idle apps and reduce benchmark noise
554                     pollfd fds[1];
555                     fds->fd = exitEvFd;
556                     fds->events = POLLIN;
557                     auto ret = TEMP_FAILURE_RETRY(poll(fds, 1, -1));
558                     RUNNER_ASSERT_ERRNO(ret > 0);
559
560                     exit(0);
561                 }
562             }
563             synchPipe.claimParentEp();
564
565             for (int i = 0; i < nConcurrentApps; i++) // wait for all kids to be ready to start measurement
566                 synchPipe.wait();
567             synchPipe.post(); // signal all kids to start measuring
568
569             for (int i = 0; i < nConcurrentApps; i++) {
570                 Timestamp ts[2];
571                 synchPipe.wait(ts, sizeof ts);
572                 candidate.emplace_back(ts[0]);
573                 prepare.emplace_back(ts[1]);
574                 everything.emplace_back(ts[0] + ts[1]);
575             }
576
577             RUNNER_ASSERT(!eventfd_write(exitEvFd, 1)); // signal all kids to exit now
578
579             for (int i = 0; i < nConcurrentApps; i++) {
580                 const auto &app = apps[i];
581                 waitPid(app.pid);
582                 Api::cleanupApp(app.hlp.getAppId(), uid, app.pid);
583             }
584         } while (Timestamp::now() < timeout);
585
586         if (!nConcurrentAppsDesc)
587             std::cerr << "additionalThreads " << int(nThreads) << ' ';
588         std::cerr << "nConcurrentApps " << int(nConcurrentApps) << " samples " << candidate.size() << '\n';
589         std::cerr << "  prepareAppCandidate [us]:              ";
590         Timestamp::report(candidate.data(), candidate.size());
591         std::cerr << "  prepareApp [us]:                       ";
592         Timestamp::report(prepare.data(), prepare.size());
593         std::cerr << "  prepareAppCandidate + prepareApp [us]: ";
594         Timestamp::report(everything.data(), everything.size());
595         candidate.clear();
596         prepare.clear();
597         everything.clear();
598     }
599 }