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