1 //
2 //
3 // Copyright 2017 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "test/core/test_util/socket_use_after_close_detector.h"
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <grpc/support/port_platform.h>
24 #include <string.h>
25
26 // IWYU pragma: no_include <arpa/inet.h>
27 // IWYU pragma: no_include <unistd.h>
28
29 #include <grpc/support/sync.h>
30
31 #include <algorithm>
32 #include <memory>
33 #include <string>
34 #include <thread>
35 #include <vector>
36
37 #include "absl/log/log.h"
38 #include "gtest/gtest.h"
39 #include "src/core/lib/iomgr/sockaddr.h"
40 #include "test/core/test_util/port.h"
41
42 #define BAD_SOCKET_RETURN_VAL (-1)
43
44 namespace {
45
46 #ifdef GPR_WINDOWS
OpenAndCloseSocketsStressLoop(int port,gpr_event * done_ev)47 void OpenAndCloseSocketsStressLoop(int port, gpr_event* done_ev) {
48 // TODO(apolcyn): re-enable this on windows if we can debug the failure.
49 // Previously, this was causing test flakes for a while b/c bind calls
50 // would fail with WSAEACCESS. Not clear if we were just making windows
51 // unhappy.
52 LOG(INFO) << "OpenAndCloseSocketsStressLoop is a no-op for windows";
53 return;
54 }
55 #else
56 void OpenAndCloseSocketsStressLoop(int port, gpr_event* done_ev) {
57 // The goal of this loop is to catch socket
58 // "use after close" bugs within the c-ares resolver by acting
59 // like some separate thread doing I/O.
60 // It's goal is to try to hit race conditions whereby:
61 // 1) The c-ares resolver closes a socket.
62 // 2) This loop opens a socket with (coincidentally) the same handle.
63 // 3) the c-ares resolver mistakenly uses that same socket without
64 // realizing that its closed.
65 // 4) This loop performs an operation on that socket that should
66 // succeed but instead fails because of what the c-ares
67 // resolver did in the meantime.
68 sockaddr_in6 addr;
69 memset(&addr, 0, sizeof(addr));
70 addr.sin6_family = AF_INET6;
71 addr.sin6_port = htons(port);
72 (reinterpret_cast<char*>(&addr.sin6_addr))[15] = 1;
73 for (;;) {
74 if (gpr_event_get(done_ev)) {
75 return;
76 }
77 std::vector<int> sockets;
78 // First open a bunch of sockets, bind and listen
79 // '50' is an arbitrary number that, experimentally,
80 // has a good chance of catching bugs.
81 for (size_t i = 0; i < 50; i++) {
82 int s = socket(AF_INET6, SOCK_STREAM, 0);
83 int val = 1;
84 ASSERT_TRUE(setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) ==
85 0)
86 << "Failed to set socketopt reuseport";
87 ASSERT_TRUE(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) ==
88 0)
89 << "Failed to set socket reuseaddr";
90 ASSERT_TRUE(fcntl(s, F_SETFL, O_NONBLOCK) == 0)
91 << "Failed to set socket non-blocking";
92 ASSERT_TRUE(s != BAD_SOCKET_RETURN_VAL)
93 << "Failed to create TCP ipv6 socket";
94 ASSERT_TRUE(bind(s, (const sockaddr*)&addr, sizeof(addr)) == 0)
95 << "Failed to bind socket " + std::to_string(s) +
96 " to [::1]:" + std::to_string(port) +
97 ". errno: " + std::to_string(errno);
98 ASSERT_TRUE(listen(s, 1) == 0) << "Failed to listen on socket " +
99 std::to_string(s) +
100 ". errno: " + std::to_string(errno);
101 sockets.push_back(s);
102 }
103 // Do a non-blocking accept followed by a close on all of those sockets.
104 // Do this in a separate loop to try to induce a time window to hit races.
105 for (size_t i = 0; i < sockets.size(); i++) {
106 if (accept(sockets[i], nullptr, nullptr)) {
107 // If e.g. a "shutdown" was called on this fd from another thread,
108 // then this accept call should fail with an unexpected error.
109 ASSERT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK)
110 << "OpenAndCloseSocketsStressLoop accept on socket " +
111 std::to_string(sockets[i]) +
112 " failed in "
113 "an unexpected way. "
114 "errno: " +
115 std::to_string(errno) +
116 ". Socket use-after-close bugs are likely.";
117 }
118 ASSERT_TRUE(close(sockets[i]) == 0)
119 << "Failed to close socket: " + std::to_string(sockets[i]) +
120 ". errno: " + std::to_string(errno);
121 }
122 }
123 }
124 #endif
125
126 } // namespace
127
128 namespace grpc_core {
129 namespace testing {
130
SocketUseAfterCloseDetector()131 SocketUseAfterCloseDetector::SocketUseAfterCloseDetector() {
132 int port = grpc_pick_unused_port_or_die();
133 gpr_event_init(&done_ev_);
134 thread_ = std::make_unique<std::thread>(OpenAndCloseSocketsStressLoop, port,
135 &done_ev_);
136 }
137
~SocketUseAfterCloseDetector()138 SocketUseAfterCloseDetector::~SocketUseAfterCloseDetector() {
139 gpr_event_set(&done_ev_, reinterpret_cast<void*>(1));
140 thread_->join();
141 }
142
143 } // namespace testing
144 } // namespace grpc_core
145