| /* Copyright 2020 The BoringSSL Authors | 
 |  * | 
 |  * Permission to use, copy, modify, and/or distribute this software for any | 
 |  * purpose with or without fee is hereby granted, provided that the above | 
 |  * copyright notice and this permission notice appear in all copies. | 
 |  * | 
 |  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
 |  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
 |  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | 
 |  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
 |  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | 
 |  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | 
 |  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ | 
 |  | 
 | #include <openssl/base.h> | 
 |  | 
 | #include "../bcm_support.h" | 
 |  | 
 | // TSAN cannot cope with this test and complains that "starting new threads | 
 | // after multi-threaded fork is not supported". | 
 | #if defined(OPENSSL_FORK_DETECTION) && !defined(OPENSSL_TSAN) && \ | 
 |     !defined(OPENSSL_IOS) | 
 | #include <errno.h> | 
 | #include <inttypes.h> | 
 | #include <stdio.h> | 
 | #include <string.h> | 
 | #include <sys/wait.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <functional> | 
 |  | 
 | #if defined(OPENSSL_THREADS) | 
 | #include <thread> | 
 | #include <vector> | 
 | #endif | 
 |  | 
 | #include <gtest/gtest.h> | 
 |  | 
 |  | 
 | static pid_t WaitpidEINTR(pid_t pid, int *out_status, int options) { | 
 |   pid_t ret; | 
 |   do { | 
 |     ret = waitpid(pid, out_status, options); | 
 |   } while (ret < 0 && errno == EINTR); | 
 |  | 
 |   return ret; | 
 | } | 
 |  | 
 | // The *InChild functions run inside a child process and must report errors via | 
 | // |stderr| and |_exit| rather than GTest. | 
 |  | 
 | static void CheckGenerationAtLeastInChild(const char *name, | 
 |                                    uint64_t minimum_expected) { | 
 |   uint64_t generation = CRYPTO_get_fork_generation(); | 
 |   if (generation < minimum_expected) { | 
 |     fprintf(stderr, "%s generation (#1) was %" PRIu64 ", wanted %" PRIu64 ".\n", | 
 |             name, generation, minimum_expected); | 
 |     _exit(1); | 
 |   } | 
 |  | 
 |   // The generation should be stable. | 
 |   uint64_t new_generation = CRYPTO_get_fork_generation(); | 
 |   if (new_generation != generation) { | 
 |     fprintf(stderr, "%s generation (#2) was %" PRIu64 ", wanted %" PRIu64 ".\n", | 
 |             name, new_generation, generation); | 
 |     _exit(1); | 
 |   } | 
 | } | 
 |  | 
 | // ForkInChild forks a child which runs |f|. If the child exits unsuccessfully, | 
 | // this function will also exit unsuccessfully. | 
 | static void ForkInChild(std::function<void()> f) { | 
 |   fflush(stderr);  // Avoid duplicating any buffered output. | 
 |  | 
 |   const pid_t pid = fork(); | 
 |   if (pid < 0) { | 
 |     perror("fork"); | 
 |     _exit(1); | 
 |   } else if (pid == 0) { | 
 |     f(); | 
 |     _exit(0); | 
 |   } | 
 |  | 
 |   // Wait for the child and pass its exit code up. | 
 |   int status; | 
 |   if (WaitpidEINTR(pid, &status, 0) < 0) { | 
 |     perror("waitpid"); | 
 |     _exit(1); | 
 |   } | 
 |   if (!WIFEXITED(status)) { | 
 |     fprintf(stderr, "Child did not exit cleanly.\n"); | 
 |     _exit(1); | 
 |   } | 
 |   if (WEXITSTATUS(status) != 0) { | 
 |     // Pass the failure up. | 
 |     _exit(WEXITSTATUS(status)); | 
 |   } | 
 | } | 
 |  | 
 | TEST(ForkDetect, Test) { | 
 |   uint64_t start = CRYPTO_get_fork_generation(); | 
 |   if (start == 0) { | 
 |     GTEST_SKIP() << "Fork detection not supported. Skipping test.\n"; | 
 |   } | 
 |  | 
 |   // The fork generation should be stable. | 
 |   EXPECT_EQ(start, CRYPTO_get_fork_generation()); | 
 |  | 
 |   fflush(stderr); | 
 |   const pid_t child = fork(); | 
 |  | 
 |   if (child == 0) { | 
 |     // Fork grandchildren before observing the fork generation. The | 
 |     // grandchildren will observe |start| + 1. | 
 |     for (int i = 0; i < 2; i++) { | 
 |       ForkInChild( | 
 |           [&] { CheckGenerationAtLeastInChild("Grandchild", start + 1); }); | 
 |     } | 
 |  | 
 |     // Now the child also observes |start| + 1. This is fine because it has | 
 |     // already diverged from the grandchild at this point. | 
 |  | 
 |     CheckGenerationAtLeastInChild("Child", start + 1); | 
 |  | 
 |     // In the pthread_atfork the value may have changed. | 
 |     uint64_t child_generation = CRYPTO_get_fork_generation(); | 
 |     // Forked grandchildren will now observe |start| + 2. | 
 |     for (int i = 0; i < 2; i++) { | 
 |       ForkInChild([&] { | 
 |         CheckGenerationAtLeastInChild("Grandchild", child_generation + 1); | 
 |       }); | 
 |     } | 
 |  | 
 | #if defined(OPENSSL_THREADS) | 
 |     // The fork generation logic itself must be thread-safe. We test this in a | 
 |     // child process to capture the actual fork detection. This segment is meant | 
 |     // to be tested in TSan. | 
 |     ForkInChild([&] { | 
 |       std::vector<std::thread> threads(4); | 
 |       for (int i = 0; i < 2; i++) { | 
 |         for (auto &t : threads) { | 
 |           t = std::thread([&] { | 
 |             CheckGenerationAtLeastInChild("Grandchild thread", | 
 |                                           child_generation + 1); | 
 |           }); | 
 |         } | 
 |         for (auto &t : threads) { | 
 |           t.join(); | 
 |         } | 
 |       } | 
 |     }); | 
 | #endif  // OPENSSL_THREADS | 
 |  | 
 |     // The child's observed value should be unchanged. | 
 |     if (child_generation != CRYPTO_get_fork_generation()) { | 
 |       fprintf(stderr, | 
 |               "Child generation (final stable check) was %" PRIu64 | 
 |               ", wanted %" PRIu64 ".\n", | 
 |               child_generation, CRYPTO_get_fork_generation()); | 
 |       _exit(1); | 
 |     } | 
 |  | 
 |     _exit(0); | 
 |   } | 
 |  | 
 |   ASSERT_GT(child, 0) << "Error in fork: " << strerror(errno); | 
 |   int status; | 
 |   ASSERT_EQ(child, WaitpidEINTR(child, &status, 0)) | 
 |       << "Error in waitpid: " << strerror(errno); | 
 |   ASSERT_TRUE(WIFEXITED(status)); | 
 |   EXPECT_EQ(0, WEXITSTATUS(status)) << "Error in child process"; | 
 |  | 
 |   // We still observe |start|. | 
 |   EXPECT_EQ(start, CRYPTO_get_fork_generation()); | 
 | } | 
 |  | 
 | #endif  // OPENSSL_FORK_DETECTION && !OPENSSL_TSAN && !OPENSSL_IOS |