1 /* Copyright (c) 2020, Google Inc.
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15 #include <openssl/base.h>
16
17 // TSAN cannot cope with this test and complains that "starting new threads
18 // after multi-threaded fork is not supported".
19 #if defined(OPENSSL_LINUX) && !defined(OPENSSL_TSAN)
20 #include <errno.h>
21 #include <inttypes.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include <functional>
27
28 #if defined(OPENSSL_THREADS)
29 #include <thread>
30 #include <vector>
31 #endif
32
33 #include <gtest/gtest.h>
34
35 #include "fork_detect.h"
36
37
WaitpidEINTR(pid_t pid,int * out_status,int options)38 static pid_t WaitpidEINTR(pid_t pid, int *out_status, int options) {
39 pid_t ret;
40 do {
41 ret = waitpid(pid, out_status, options);
42 } while (ret < 0 && errno == EINTR);
43
44 return ret;
45 }
46
47 // The *InChild functions run inside a child process and must report errors via
48 // |stderr| and |_exit| rather than GTest.
49
CheckGenerationInChild(const char * name,uint64_t expected)50 static void CheckGenerationInChild(const char *name, uint64_t expected) {
51 uint64_t generation = CRYPTO_get_fork_generation();
52 if (generation != expected) {
53 fprintf(stderr, "%s generation (#1) was %" PRIu64 ", wanted %" PRIu64 ".\n",
54 name, generation, expected);
55 _exit(1);
56 }
57
58 // The generation should be stable.
59 generation = CRYPTO_get_fork_generation();
60 if (generation != expected) {
61 fprintf(stderr, "%s generation (#2) was %" PRIu64 ", wanted %" PRIu64 ".\n",
62 name, generation, expected);
63 _exit(1);
64 }
65 }
66
67 // ForkInChild forks a child which runs |f|. If the child exits unsuccessfully,
68 // this function will also exit unsuccessfully.
ForkInChild(std::function<void ()> f)69 static void ForkInChild(std::function<void()> f) {
70 fflush(stderr); // Avoid duplicating any buffered output.
71
72 const pid_t pid = fork();
73 if (pid < 0) {
74 perror("fork");
75 _exit(1);
76 } else if (pid == 0) {
77 f();
78 _exit(0);
79 }
80
81 // Wait for the child and pass its exit code up.
82 int status;
83 if (WaitpidEINTR(pid, &status, 0) < 0) {
84 perror("waitpid");
85 _exit(1);
86 }
87 if (!WIFEXITED(status)) {
88 fprintf(stderr, "Child did not exit cleanly.\n");
89 _exit(1);
90 }
91 if (WEXITSTATUS(status) != 0) {
92 // Pass the failure up.
93 _exit(WEXITSTATUS(status));
94 }
95 }
96
TEST(ForkDetect,Test)97 TEST(ForkDetect, Test) {
98 const uint64_t start = CRYPTO_get_fork_generation();
99 if (start == 0) {
100 fprintf(stderr, "Fork detection not supported. Skipping test.\n");
101 return;
102 }
103
104 // The fork generation should be stable.
105 EXPECT_EQ(start, CRYPTO_get_fork_generation());
106
107 fflush(stderr);
108 const pid_t child = fork();
109
110 if (child == 0) {
111 // Fork grandchildren before observing the fork generation. The
112 // grandchildren will observe |start| + 1.
113 for (int i = 0; i < 2; i++) {
114 ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 1); });
115 }
116
117 // Now the child also observes |start| + 1. This is fine because it has
118 // already diverged from the grandchild at this point.
119 CheckGenerationInChild("Child", start + 1);
120
121 // Forked grandchildren will now observe |start| + 2.
122 for (int i = 0; i < 2; i++) {
123 ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 2); });
124 }
125
126 #if defined(OPENSSL_THREADS)
127 // The fork generation logic itself must be thread-safe. We test this in a
128 // child process to capture the actual fork detection. This segment is meant
129 // to be tested in TSan.
130 ForkInChild([&] {
131 std::vector<std::thread> threads(4);
132 for (int i = 0; i < 2; i++) {
133 for (auto &t : threads) {
134 t = std::thread(
135 [&] { CheckGenerationInChild("Grandchild thread", start + 2); });
136 }
137 for (auto &t : threads) {
138 t.join();
139 }
140 }
141 });
142 #endif // OPENSSL_THREADS
143
144 // The child still observes |start| + 1.
145 CheckGenerationInChild("Child", start + 1);
146 _exit(0);
147 }
148
149 ASSERT_GT(child, 0) << "Error in fork: " << strerror(errno);
150 int status;
151 ASSERT_EQ(child, WaitpidEINTR(child, &status, 0))
152 << "Error in waitpid: " << strerror(errno);
153 ASSERT_TRUE(WIFEXITED(status));
154 EXPECT_EQ(0, WEXITSTATUS(status)) << "Error in child process";
155
156 // We still observe |start|.
157 EXPECT_EQ(start, CRYPTO_get_fork_generation());
158 }
159
160 #endif // OPENSSL_LINUX && !OPENSSL_TSAN
161