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/util/socket_use_after_close_detector.h"
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <string.h>
24
25 #include <grpc/support/port_platform.h>
26
27 // IWYU pragma: no_include <arpa/inet.h>
28 // IWYU pragma: no_include <unistd.h>
29
30 #include <algorithm>
31 #include <memory>
32 #include <string>
33 #include <thread>
34 #include <vector>
35
36 #include "gtest/gtest.h"
37
38 #include <grpc/support/sync.h>
39
40 #include "src/core/lib/iomgr/sockaddr.h"
41 #include "test/core/util/port.h"
42
43 // TODO(unknown): pull in different headers when enabling this
44 // test on windows. Also set BAD_SOCKET_RETURN_VAL
45 // to INVALID_SOCKET on windows.
46 #ifdef GPR_WINDOWS
47 #include "src/core/lib/iomgr/socket_windows.h"
48 #include "src/core/lib/iomgr/tcp_windows.h"
49
50 #define BAD_SOCKET_RETURN_VAL INVALID_SOCKET
51 #else
52 #define BAD_SOCKET_RETURN_VAL (-1)
53 #endif
54
55 namespace {
56
57 #ifdef GPR_WINDOWS
OpenAndCloseSocketsStressLoop(int port,gpr_event * done_ev)58 void OpenAndCloseSocketsStressLoop(int port, gpr_event* done_ev) {
59 sockaddr_in6 addr;
60 memset(&addr, 0, sizeof(addr));
61 addr.sin6_family = AF_INET6;
62 addr.sin6_port = htons(port);
63 ((char*)&addr.sin6_addr)[15] = 1;
64 for (;;) {
65 if (gpr_event_get(done_ev)) {
66 return;
67 }
68 std::vector<int> sockets;
69 for (size_t i = 0; i < 50; i++) {
70 SOCKET s = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, nullptr, 0,
71 WSA_FLAG_OVERLAPPED);
72 ASSERT_TRUE(s != BAD_SOCKET_RETURN_VAL)
73 << "Failed to create TCP ipv6 socket";
74 char val = 1;
75 ASSERT_TRUE(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) !=
76 SOCKET_ERROR)
77 << "Failed to set socketopt reuseaddr. WSA error: " +
78 std::to_string(WSAGetLastError());
79 ASSERT_TRUE(grpc_tcp_set_non_block(s) == absl::OkStatus())
80 << "Failed to set socket non-blocking";
81 ASSERT_TRUE(bind(s, (const sockaddr*)&addr, sizeof(addr)) != SOCKET_ERROR)
82 << "Failed to bind socket " + std::to_string(s) +
83 " to [::1]:" + std::to_string(port) +
84 ". WSA error: " + std::to_string(WSAGetLastError());
85 ASSERT_TRUE(listen(s, 1) != SOCKET_ERROR)
86 << "Failed to listen on socket " + std::to_string(s) +
87 ". WSA error: " + std::to_string(WSAGetLastError());
88 sockets.push_back(s);
89 }
90 // Do a non-blocking accept followed by a close on all of those sockets.
91 // Do this in a separate loop to try to induce a time window to hit races.
92 for (size_t i = 0; i < sockets.size(); i++) {
93 ASSERT_TRUE(accept(sockets[i], nullptr, nullptr) == INVALID_SOCKET)
94 << "Accept on phony socket unexpectedly accepted actual connection.";
95 ASSERT_TRUE(WSAGetLastError() == WSAEWOULDBLOCK)
96 << "OpenAndCloseSocketsStressLoop accept on socket " +
97 std::to_string(sockets[i]) +
98 " failed in "
99 "an unexpected way. "
100 "WSA error: " +
101 std::to_string(WSAGetLastError()) +
102 ". Socket use-after-close bugs are likely.";
103 ASSERT_TRUE(closesocket(sockets[i]) != SOCKET_ERROR)
104 << "Failed to close socket: " + std::to_string(sockets[i]) +
105 ". WSA error: " + std::to_string(WSAGetLastError());
106 }
107 }
108 return;
109 }
110 #else
111 void OpenAndCloseSocketsStressLoop(int port, gpr_event* done_ev) {
112 // The goal of this loop is to catch socket
113 // "use after close" bugs within the c-ares resolver by acting
114 // like some separate thread doing I/O.
115 // It's goal is to try to hit race conditions whereby:
116 // 1) The c-ares resolver closes a socket.
117 // 2) This loop opens a socket with (coincidentally) the same handle.
118 // 3) the c-ares resolver mistakenly uses that same socket without
119 // realizing that its closed.
120 // 4) This loop performs an operation on that socket that should
121 // succeed but instead fails because of what the c-ares
122 // resolver did in the meantime.
123 sockaddr_in6 addr;
124 memset(&addr, 0, sizeof(addr));
125 addr.sin6_family = AF_INET6;
126 addr.sin6_port = htons(port);
127 (reinterpret_cast<char*>(&addr.sin6_addr))[15] = 1;
128 for (;;) {
129 if (gpr_event_get(done_ev)) {
130 return;
131 }
132 std::vector<int> sockets;
133 // First open a bunch of sockets, bind and listen
134 // '50' is an arbitrary number that, experimentally,
135 // has a good chance of catching bugs.
136 for (size_t i = 0; i < 50; i++) {
137 int s = socket(AF_INET6, SOCK_STREAM, 0);
138 int val = 1;
139 ASSERT_TRUE(setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) ==
140 0)
141 << "Failed to set socketopt reuseport";
142 ASSERT_TRUE(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) ==
143 0)
144 << "Failed to set socket reuseaddr";
145 ASSERT_TRUE(fcntl(s, F_SETFL, O_NONBLOCK) == 0)
146 << "Failed to set socket non-blocking";
147 ASSERT_TRUE(s != BAD_SOCKET_RETURN_VAL)
148 << "Failed to create TCP ipv6 socket";
149 ASSERT_TRUE(bind(s, (const sockaddr*)&addr, sizeof(addr)) == 0)
150 << "Failed to bind socket " + std::to_string(s) +
151 " to [::1]:" + std::to_string(port) +
152 ". errno: " + std::to_string(errno);
153 ASSERT_TRUE(listen(s, 1) == 0) << "Failed to listen on socket " +
154 std::to_string(s) +
155 ". errno: " + std::to_string(errno);
156 sockets.push_back(s);
157 }
158 // Do a non-blocking accept followed by a close on all of those sockets.
159 // Do this in a separate loop to try to induce a time window to hit races.
160 for (size_t i = 0; i < sockets.size(); i++) {
161 if (accept(sockets[i], nullptr, nullptr)) {
162 // If e.g. a "shutdown" was called on this fd from another thread,
163 // then this accept call should fail with an unexpected error.
164 ASSERT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK)
165 << "OpenAndCloseSocketsStressLoop accept on socket " +
166 std::to_string(sockets[i]) +
167 " failed in "
168 "an unexpected way. "
169 "errno: " +
170 std::to_string(errno) +
171 ". Socket use-after-close bugs are likely.";
172 }
173 ASSERT_TRUE(close(sockets[i]) == 0)
174 << "Failed to close socket: " + std::to_string(sockets[i]) +
175 ". errno: " + std::to_string(errno);
176 }
177 }
178 }
179 #endif
180
181 } // namespace
182
183 namespace grpc_core {
184 namespace testing {
185
SocketUseAfterCloseDetector()186 SocketUseAfterCloseDetector::SocketUseAfterCloseDetector() {
187 int port = grpc_pick_unused_port_or_die();
188 gpr_event_init(&done_ev_);
189 thread_ = std::make_unique<std::thread>(OpenAndCloseSocketsStressLoop, port,
190 &done_ev_);
191 }
192
~SocketUseAfterCloseDetector()193 SocketUseAfterCloseDetector::~SocketUseAfterCloseDetector() {
194 gpr_event_set(&done_ev_, reinterpret_cast<void*>(1));
195 thread_->join();
196 }
197
198 } // namespace testing
199 } // namespace grpc_core
200