-
Notifications
You must be signed in to change notification settings - Fork 172
Expand file tree
/
Copy pathmain.cpp
More file actions
308 lines (269 loc) · 9.52 KB
/
main.cpp
File metadata and controls
308 lines (269 loc) · 9.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include "config.hpp"
#include "runner.hpp"
#include "subscriber/waf.hpp"
#include <csignal>
#include <cstddef>
#include <cstdlib>
#include <functional>
#include <memory>
#include <spdlog/common.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/spdlog.h>
#include <string_view>
extern "C" {
#include <fcntl.h>
#include <pthread.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
}
namespace {
constexpr std::chrono::seconds log_flush_interval{5};
std::atomic<bool> interrupted; // NOLINT
std::atomic<bool> finished; // NOLINT
pthread_t thread_handle;
void *pthread_wrapper(void *arg)
{
auto *func = static_cast<std::function<int8_t()> *>(arg);
int8_t ret = 0;
try {
(*func)();
} catch (const std::exception &e) {
SPDLOG_ERROR("Exception in pthread wrapper: {}", e.what());
ret = 1;
} catch (...) {
SPDLOG_ERROR("Unknown exception in pthread wrapper");
ret = 1;
}
try {
delete func; // NOLINT(cppcoreguidelines-owning-memory)
} catch (...) {
SPDLOG_ERROR("Unknown exception in func delete in pthread wrapper");
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
return reinterpret_cast<void *>(ret);
}
bool ensure_unique_lock(const std::string &lock_path)
{
// do not acquire the lock / assume we inherited it
if (lock_path == "-") {
return true;
}
/** The first helper process will create and exclusively lock the file */
// NOLINTNEXTLINE
const int fd = ::open(lock_path.c_str(), O_WRONLY | O_CREAT, 0744);
if (fd == -1) {
SPDLOG_INFO("Failed to open lock file: {}", lock_path);
return false;
}
SPDLOG_DEBUG("Opened lock file {}: fd {}", lock_path, fd);
int const res = ::flock(fd, LOCK_EX | LOCK_NB);
// If we fail to obtain the lock, for whichever reason, assume we can't
// run for now.
if (res == -1) {
::close(fd);
SPDLOG_INFO("Failed to get exclusive lock on file {}: errno {}",
lock_path, errno);
return false;
}
return true;
}
bool ensure_unique_abstract_socket(std::string_view socket_path)
{
if (socket_path.empty() || socket_path[0] != '@') {
return true;
}
// Try to connect to the socket. If successful, another helper is running
// NOLINTNEXTLINE(android-cloexec-socket)
int const sock = ::socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
SPDLOG_INFO("Failed to create test socket: errno {}", errno);
return false;
}
struct sockaddr_un addr {};
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0';
std::size_t const size =
std::min(socket_path.size() - 1, sizeof(addr.sun_path) - 2);
std::copy_n(socket_path.data() + 1, size, &addr.sun_path[1]);
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
addr.sun_path[size + 1] = '\0';
int const res = ::connect(
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
::close(sock);
if (res == 0) {
SPDLOG_INFO("Another helper is already running on {}", socket_path);
return false;
}
SPDLOG_DEBUG("No helper running on socket {}, proceeding", socket_path);
return true;
}
int appsec_helper_main_impl()
{
dds::config::config const config{
[](std::string_view key) -> std::optional<std::string_view> {
// NOLINTNEXTLINE
char const *value = std::getenv(key.data());
if (value == nullptr) {
return std::nullopt;
}
return std::string_view{value};
}};
std::shared_ptr<spdlog::logger> logger;
bool stderr_fallback = false;
try {
logger = spdlog::basic_logger_mt(
"ddappsec", std::string{config.log_file_path()}, false);
} catch (std::exception &e) {
// NOLINTNEXTLINE
logger = spdlog::stderr_logger_mt("ddappsec");
stderr_fallback = true;
}
spdlog::set_default_logger(logger);
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l][%t] %v");
auto level = config.log_level();
spdlog::set_level(level);
spdlog::flush_on(level);
if (stderr_fallback) {
logger->warn("Failed to open log file {}, falling back to stderr",
config.log_file_path());
}
dds::waf::initialise_logging(level);
if (config.is_abstract_socket()) {
if (!ensure_unique_abstract_socket(config.socket_file_path())) {
logger->warn("helper launched, but not unique (abstract socket "
"exists), exiting");
return 1;
}
} else {
if (!ensure_unique_lock(std::string{config.lock_file_path()})) {
logger->warn(
"helper launched, but not unique (lock file exists), exiting");
return 1;
}
}
// block SIGUSR1 (only used to interrupt the runner)
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
if (auto err = pthread_sigmask(SIG_BLOCK, &mask, nullptr)) {
SPDLOG_ERROR("Failed to block SIGUSR1: error number {}", err);
return 1;
}
dds::remote_config::resolve_symbols();
dds::runner::resolve_symbols();
dds::service::resolve_symbols();
auto runner = std::make_shared<dds::runner>(config, interrupted);
SPDLOG_INFO("starting runner on new thread");
auto thread_function = std::make_unique<std::function<int8_t()>>(
[runner = std::move(runner)]() -> int8_t {
#ifdef __linux__
pthread_setname_np(pthread_self(), "appsec_helper runner");
#elif defined(__APPLE__)
pthread_setname_np("appsec_helper runner");
#endif
runner->register_for_rc_notifications();
runner->run();
runner->unregister_for_rc_notifications();
finished.store(true, std::memory_order_release);
return 0;
});
// Set up pthread attributes with 8 MB stack size
pthread_attr_t attr;
if (int err = pthread_attr_init(&attr)) {
SPDLOG_ERROR(
"Failed to initialize pthread attributes: error number {}", err);
return 1;
}
const dds::defer defer_attr_destroy{
[&attr]() { pthread_attr_destroy(&attr); }};
constexpr auto stack_size =
static_cast<const size_t>(8 * 1024 * 1024); // 8 MB
if (int err = pthread_attr_setstacksize(&attr, stack_size)) {
SPDLOG_ERROR("Failed to set pthread stack size: error number {}", err);
return 1;
}
if (int err = pthread_create(&thread_handle, &attr, pthread_wrapper,
thread_function.release())) {
SPDLOG_ERROR("Failed to create pthread: error number {}", err);
return 1;
}
return 0;
}
} // namespace
extern "C" __attribute__((visibility("default"))) int
appsec_helper_main() noexcept
{
try {
return appsec_helper_main_impl();
} catch (std::exception &e) {
SPDLOG_ERROR("Unhandled exception: {}", e.what());
return 2;
} catch (...) {
SPDLOG_ERROR("Unhandled exception");
return 2;
}
return 0;
}
extern "C" __attribute__((visibility("default"))) int
appsec_helper_shutdown() noexcept
{
interrupted.store(true, std::memory_order_release);
pthread_kill(thread_handle, SIGUSR1);
// wait up to 1 second for the runner to finish
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
bool had_finished = false;
while (true) {
if (!had_finished && finished.load(std::memory_order_acquire)) {
SPDLOG_INFO("AppSec helper finished");
had_finished = true;
}
// after finished is written to, we haven't necessarily destroyed the
// std::function and its captured variables. This needs to happen before
// the helper shared library is unloaded by trampoline.c.
// Wait for the joinable thread to actually exit (with a timeout).
void *thr_exit_status;
#ifdef __APPLE__
// macOS doesn't have pthread_tryjoin_np, so we check the deadline first
// and then do a blocking join if the thread has finished
if (had_finished) {
// Thread has signaled finished, do a blocking join
const int res = pthread_join(thread_handle, &thr_exit_status);
if (res == 0) {
SPDLOG_INFO("AppSec helper thread has exited");
break;
}
}
#else
const int res = pthread_tryjoin_np(thread_handle, &thr_exit_status);
if (res == 0) {
SPDLOG_INFO("AppSec helper thread has exited");
break;
}
#endif
if (std::chrono::steady_clock::now() >= deadline) {
// we need to call _exit() to avoid a segfault in the still running
// helper threads after the helper shared library is unloaded by
// trampoline.c
SPDLOG_WARN("Could not finish AppSec helper before deadline. "
"Calling _exit().");
_exit(EXIT_FAILURE); // NOLINT
__builtin_unreachable();
}
std::this_thread::sleep_for(std::chrono::milliseconds{10}); // NOLINT
}
spdlog::shutdown();
return 0;
}