// Copyright 2020 The BoringSSL Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #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 #include #include #include #include #include #include #if defined(OPENSSL_THREADS) #include #include #endif #include 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 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 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