Skip to content

Commit 575faf4

Browse files
authored
Merge pull request #3525 from DataDog/glopes/appsec-abstract-ns
Appsec: use abstract namespace on linux
2 parents 560e003 + 72f5047 commit 575faf4

File tree

17 files changed

+276
-110
lines changed

17 files changed

+276
-110
lines changed

.gitlab/generate-appsec.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
before_script:
6464
<?php echo $ecrLoginSnippet, "\n"; ?>
6565
<?php dockerhub_login() ?>
66-
- apt update && apt install -y default-jre
66+
- apt update && apt install -y openjdk-17-jre
6767

6868
"test appsec extension":
6969
stage: test
@@ -130,7 +130,7 @@
130130
<?php echo $ecrLoginSnippet, "\n"; ?>
131131
<?php dockerhub_login() ?>
132132
script:
133-
- apt update && apt install -y default-jre
133+
- apt update && apt install -y openjdk-17-jre
134134
- find "$CI_PROJECT_DIR"/appsec/tests/integration/build || true
135135
- |
136136
cd appsec/tests/integration

appsec/run-tests-internal.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -845,19 +845,19 @@ function write_information()
845845
$info_params = array();
846846
settings2array($ini_overwrites, $info_params);
847847
$info_params = settings2params($info_params);
848-
$php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`;
849-
define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`);
848+
$php_info = shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\"");
849+
define('TESTED_PHP_VERSION', shell_exec("$php -n -r \"echo PHP_VERSION;\""));
850850

851851
if ($php_cgi && $php != $php_cgi) {
852-
$php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`;
852+
$php_info_cgi = shell_exec("$php_cgi $pass_options $info_params $no_file_cache -q \"$info_file\"");
853853
$php_info_sep = "\n---------------------------------------------------------------------";
854854
$php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep";
855855
} else {
856856
$php_cgi_info = '';
857857
}
858858

859859
if ($phpdbg) {
860-
$phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`;
860+
$phpdbg_info = shell_exec("$phpdbg $pass_options $info_params $no_file_cache -qrr \"$info_file\"");
861861
$php_info_sep = "\n---------------------------------------------------------------------";
862862
$phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep";
863863
} else {
@@ -872,7 +872,7 @@ function write_information()
872872
// load list of enabled extensions
873873
save_text($info_file,
874874
'<?php echo str_replace("Zend OPcache", "opcache", implode(",", get_loaded_extensions())); ?>');
875-
$exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`);
875+
$exts_to_test = explode(',', shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\""));
876876
// check for extensions that need special handling and regenerate
877877
$info_params_ex = array(
878878
'session' => array('session.auto_start=0'),
@@ -2171,9 +2171,9 @@ function run_test($php, $file, array $env)
21712171
$ext_params = array();
21722172
settings2array($ini_overwrites, $ext_params);
21732173
$ext_params = settings2params($ext_params);
2174-
$ext_dir = `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo ini_get('extension_dir');"`;
2174+
$ext_dir = shell_exec("$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r \"echo ini_get('extension_dir');\"");
21752175
$extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS']));
2176-
$loaded = explode(",", `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`);
2176+
$loaded = explode(",", shell_exec("$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r \"echo implode(',', get_loaded_extensions());\""));
21772177
$ext_prefix = IS_WINDOWS ? "php_" : "";
21782178
foreach ($extensions as $req_ext) {
21792179
if (!in_array($req_ext, $loaded)) {
@@ -3403,7 +3403,7 @@ function show_result(
34033403
$tested,
34043404
$tested_file,
34053405
$extra = '',
3406-
array $temp_filenames = null
3406+
$temp_filenames = null
34073407
) {
34083408
global $SHOW_ONLY_GROUPS, $colorize;
34093409

appsec/src/extension/configuration.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extern bool runtime_config_first_init;
2929
#define DD_BASE(path) "/opt/datadog-php/"
3030

3131
// clang-format off
32-
#define DD_CONFIGURATION \
32+
#define DD_CONFIGURATION_GENERAL \
3333
SYSCFG(BOOL, DD_APPSEC_ENABLED, "false") \
3434
SYSCFG(BOOL, DD_APPSEC_CLI_START_ON_RINIT, "false") \
3535
SYSCFG(STRING, DD_APPSEC_RULES, "") \
@@ -50,7 +50,6 @@ extern bool runtime_config_first_init;
5050
SYSCFG(BOOL, DD_APPSEC_RASP_ENABLED , "true") \
5151
SYSCFG(INT, DD_APPSEC_MAX_STACK_TRACE_DEPTH, "32") \
5252
SYSCFG(INT, DD_APPSEC_MAX_STACK_TRACES, "2") \
53-
CONFIG(STRING, DD_APPSEC_HELPER_RUNTIME_PATH, "/tmp", .ini_change = dd_on_runtime_path_update) \
5453
SYSCFG(STRING, DD_APPSEC_HELPER_LOG_FILE, "/dev/null") \
5554
SYSCFG(STRING, DD_APPSEC_HELPER_LOG_LEVEL, "info") \
5655
CONFIG(CUSTOM(SET), DD_EXTRA_SERVICES, "", .parser = _parse_list) \
@@ -71,7 +70,17 @@ extern bool runtime_config_first_init;
7170
CONFIG(STRING, DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON, "") \
7271
CONFIG(BOOL, DD_APM_TRACING_ENABLED, "true") \
7372
CONFIG(BOOL, DD_API_SECURITY_ENABLED, "true", .ini_change = zai_config_system_ini_change) \
74-
CONFIG(DOUBLE, DD_API_SECURITY_SAMPLE_DELAY, "30.0", .ini_change = zai_config_system_ini_change) \
73+
CONFIG(DOUBLE, DD_API_SECURITY_SAMPLE_DELAY, "30.0", .ini_change = zai_config_system_ini_change)
74+
75+
#ifdef __linux__
76+
#define DD_CONFIGURATION \
77+
DD_CONFIGURATION_GENERAL \
78+
CONFIG(STRING, DD_APPSEC_HELPER_RUNTIME_PATH, "@/ddappsec", .ini_change = dd_on_runtime_path_update)
79+
#else
80+
#define DD_CONFIGURATION \
81+
DD_CONFIGURATION_GENERAL \
82+
CONFIG(STRING, DD_APPSEC_HELPER_RUNTIME_PATH, "/tmp", .ini_change = dd_on_runtime_path_update)
83+
#endif
7584
// clang-format on
7685

7786
#define CALIAS CONFIG

appsec/src/extension/helper_process.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ typedef struct _dd_helper_mgr {
4141
bool connected_this_req;
4242
dd_helper_shared_state hss;
4343

44-
char *nonnull socket_path;
45-
char *nonnull lock_path;
44+
char *nonnull socket_path; // if abstract, starts with @
45+
char *nonnull lock_path; // set, but not used with abstract ns sockets
4646
} dd_helper_mgr;
4747

4848
static _Atomic(dd_helper_shared_state) *_shared_state;

appsec/src/extension/network.c

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,13 @@ static long _timespec_delta_ms(struct timespec *ts1, struct timespec *ts2);
4949
int dd_conn_init( // NOLINT(readability-function-cognitive-complexity)
5050
dd_conn *nonnull conn, const char *nonnull path, size_t path_len)
5151
{
52-
if (path_len > sizeof(conn->addr.sun_path) - 1) {
52+
if (path_len < 1) {
53+
mlog(dd_log_error, "Socket path is too short");
54+
return dd_error;
55+
}
56+
const bool is_abstract = path[0] == '@';
57+
58+
if (path_len > sizeof(conn->addr.sun_path) - (is_abstract ? 0 : 1)) {
5359
mlog(dd_log_error, "Socket path is too long");
5460
return dd_error;
5561
}
@@ -64,9 +70,19 @@ int dd_conn_init( // NOLINT(readability-function-cognitive-complexity)
6470

6571
conn->addr.sun_family = AF_UNIX;
6672

67-
// NOLINTNEXTLINE
68-
strncpy(conn->addr.sun_path, path, sizeof(conn->addr.sun_path) - 1);
69-
conn->addr.sun_path[sizeof(conn->addr.sun_path) - 1] = '\0';
73+
size_t addr_size;
74+
if (is_abstract) {
75+
// abstract namespace socket; replace @ with NUL byte
76+
conn->addr.sun_path[0] = '\0';
77+
strncpy(
78+
conn->addr.sun_path + 1, path + 1, sizeof(conn->addr.sun_path) - 2);
79+
conn->addr.sun_path[sizeof(conn->addr.sun_path) - 1] = '\0';
80+
addr_size = path_len + offsetof(struct sockaddr_un, sun_path);
81+
} else {
82+
strncpy(conn->addr.sun_path, path, sizeof(conn->addr.sun_path) - 1);
83+
conn->addr.sun_path[sizeof(conn->addr.sun_path) - 1] = '\0';
84+
addr_size = sizeof(conn->addr);
85+
}
7086

7187
int flags_before = fcntl(conn->socket, F_GETFL, 0);
7288
if (flags_before == -1) {
@@ -82,19 +98,18 @@ int dd_conn_init( // NOLINT(readability-function-cognitive-complexity)
8298

8399
mlog(dd_log_info, "Attempting to connect to UNIX socket %s", path);
84100
struct timespec deadline;
85-
clock_gettime(CLOCK_MONOTONIC, &deadline);
101+
UNUSED(clock_gettime(CLOCK_MONOTONIC, &deadline));
86102
_timespec_add_ms(&deadline, CONNECT_TIMEOUT);
87103

88104
try_again:
89-
res = connect(
90-
conn->socket, (struct sockaddr *)&conn->addr, sizeof(conn->addr));
105+
res = connect(conn->socket, (struct sockaddr *)&conn->addr, addr_size);
91106
if (res == -1) {
92107
int errno_copy = errno;
93108
if (errno_copy == EINPROGRESS) {
94109
struct pollfd pfds[] = {
95110
{.fd = conn->socket, .events = POLLIN | POLLOUT}};
96111
struct timespec now;
97-
clock_gettime(CLOCK_MONOTONIC, &now);
112+
UNUSED(clock_gettime(CLOCK_MONOTONIC, &now));
98113
long time_left = _timespec_delta_ms(&deadline, &now);
99114
if (time_left <= 0) {
100115
dd_conn_destroy(conn);
@@ -131,7 +146,7 @@ int dd_conn_init( // NOLINT(readability-function-cognitive-complexity)
131146
if (errno_copy == ENOENT || errno_copy == ECONNREFUSED) {
132147
// the socket does not exist or is not being listened on. Retry
133148
struct timespec now;
134-
clock_gettime(CLOCK_MONOTONIC, &now);
149+
UNUSED(clock_gettime(CLOCK_MONOTONIC, &now));
135150
long time_left = _timespec_delta_ms(&deadline, &now);
136151
if (time_left <= 0) {
137152
dd_conn_destroy(conn);

appsec/src/helper/config.hpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
66
#pragma once
77

8-
#include <boost/lexical_cast.hpp>
9-
#include <chrono>
108
#include <optional>
119
#include <spdlog/spdlog.h>
1210
#include <string_view>
@@ -26,6 +24,15 @@ class config {
2624
return kv_.at(env_socket_file_path);
2725
}
2826

27+
[[nodiscard]] bool is_abstract_socket() const
28+
{
29+
std::string_view const sfp = socket_file_path();
30+
if (sfp.empty()) {
31+
return false;
32+
}
33+
return sfp[0] == '@';
34+
}
35+
2936
[[nodiscard]] std::string_view lock_file_path() const
3037
{
3138
return kv_.at(env_lock_file_path);

appsec/src/helper/main.cpp

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ extern "C" {
2525
#include <fcntl.h>
2626
#include <pthread.h>
2727
#include <sys/file.h>
28+
#include <sys/socket.h>
2829
#include <sys/stat.h>
2930
#include <sys/types.h>
31+
#include <sys/un.h>
3032
}
3133

3234
namespace {
@@ -60,7 +62,7 @@ void *pthread_wrapper(void *arg)
6062
return reinterpret_cast<void *>(ret);
6163
}
6264

63-
bool ensure_unique(const std::string &lock_path)
65+
bool ensure_unique_lock(const std::string &lock_path)
6466
{
6567
// do not acquire the lock / assume we inherited it
6668
if (lock_path == "-") {
@@ -88,6 +90,44 @@ bool ensure_unique(const std::string &lock_path)
8890
return true;
8991
}
9092

93+
bool ensure_unique_abstract_socket(std::string_view socket_path)
94+
{
95+
if (socket_path.empty() || socket_path[0] != '@') {
96+
return true;
97+
}
98+
99+
// Try to connect to the socket. If successful, another helper is running
100+
// NOLINTNEXTLINE(android-cloexec-socket)
101+
int const sock = ::socket(AF_UNIX, SOCK_STREAM, 0);
102+
if (sock == -1) {
103+
SPDLOG_INFO("Failed to create test socket: errno {}", errno);
104+
return false;
105+
}
106+
107+
struct sockaddr_un addr {};
108+
addr.sun_family = AF_UNIX;
109+
addr.sun_path[0] = '\0';
110+
111+
std::size_t const size =
112+
std::min(socket_path.size() - 1, sizeof(addr.sun_path) - 2);
113+
std::copy_n(socket_path.data() + 1, size, &addr.sun_path[1]);
114+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
115+
addr.sun_path[size + 1] = '\0';
116+
117+
int const res = ::connect(
118+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
119+
sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
120+
::close(sock);
121+
122+
if (res == 0) {
123+
SPDLOG_INFO("Another helper is already running on {}", socket_path);
124+
return false;
125+
}
126+
127+
SPDLOG_DEBUG("No helper running on socket {}, proceeding", socket_path);
128+
return true;
129+
}
130+
91131
int appsec_helper_main_impl()
92132
{
93133
dds::config::config const config{
@@ -124,10 +164,18 @@ int appsec_helper_main_impl()
124164

125165
dds::waf::initialise_logging(level);
126166

127-
if (!ensure_unique(std::string{config.lock_file_path()})) {
128-
logger->warn("helper launched, but not unique, exiting");
129-
// There's another helper running
130-
return 1;
167+
if (config.is_abstract_socket()) {
168+
if (!ensure_unique_abstract_socket(config.socket_file_path())) {
169+
logger->warn("helper launched, but not unique (abstract socket "
170+
"exists), exiting");
171+
return 1;
172+
}
173+
} else {
174+
if (!ensure_unique_lock(std::string{config.lock_file_path()})) {
175+
logger->warn(
176+
"helper launched, but not unique (lock file exists), exiting");
177+
return 1;
178+
}
131179
}
132180

133181
// block SIGUSR1 (only used to interrupt the runner)

appsec/src/helper/network/acceptor.cpp

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,42 +29,74 @@ acceptor::acceptor(const std::string_view &sv)
2929
}
3030

3131
struct sockaddr_un addr {};
32+
std::size_t addr_size;
3233
addr.sun_family = AF_UNIX;
33-
if (sv.size() > sizeof(addr.sun_path) - 1) {
34-
throw std::invalid_argument{"socket path too long"};
35-
}
36-
strcpy(static_cast<char *>(addr.sun_path), sv.data()); // NOLINT
34+
bool const is_abstract = (!sv.empty() && sv[0] == '@');
3735

38-
// Remove the existing socket
39-
int res = ::unlink(static_cast<char *>(addr.sun_path));
40-
if (res == -1 && errno != ENOENT) {
41-
SPDLOG_ERROR("Failed to unlink {}: errno {}", addr.sun_path, errno);
42-
throw std::system_error(errno, std::generic_category());
36+
if (is_abstract) {
37+
#ifdef __linux__
38+
if (sv.size() > sizeof(addr.sun_path)) {
39+
throw std::invalid_argument{"socket path too long"};
40+
}
41+
// Replace @ with null byte for abstract namespace
42+
addr.sun_path[0] = '\0';
43+
std::copy_n(sv.data() + 1, sv.size() - 1, &addr.sun_path[1]);
44+
addr_size = sv.size() + offsetof(struct sockaddr_un, sun_path);
45+
#else
46+
throw std::runtime_error{
47+
"Abstract namespace sockets are only supported on Linux"};
48+
#endif
49+
} else {
50+
// Filesystem socket
51+
if (sv.size() > sizeof(addr.sun_path) - 1) {
52+
throw std::invalid_argument{"socket path too long"};
53+
}
54+
std::copy_n(sv.data(), sv.size(), &addr.sun_path[0]);
55+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
56+
addr.sun_path[sv.size()] = '\0';
57+
addr_size = sizeof(addr);
58+
59+
// Remove the existing socket
60+
int const res = ::unlink(static_cast<char *>(addr.sun_path));
61+
if (res == -1 && errno != ENOENT) {
62+
SPDLOG_ERROR("Failed to unlink {}: errno {}", addr.sun_path, errno);
63+
throw std::system_error(errno, std::generic_category());
64+
}
65+
SPDLOG_DEBUG("Unlinked {}", addr.sun_path);
4366
}
44-
SPDLOG_DEBUG("Unlinked {}", addr.sun_path);
4567

46-
res =
68+
int res = ::bind(
4769
// NOLINTNEXTLINE
48-
::bind(sock_.get(), reinterpret_cast<struct sockaddr *>(&addr),
49-
sizeof(addr));
70+
sock_.get(), reinterpret_cast<struct sockaddr *>(&addr), addr_size);
5071
if (res == -1) {
51-
SPDLOG_ERROR(
52-
"Failed to bind socket to {}: errno {}", addr.sun_path, errno);
72+
if (is_abstract) {
73+
SPDLOG_ERROR("Failed to bind abstract socket: errno {}", errno);
74+
} else {
75+
SPDLOG_ERROR(
76+
"Failed to bind socket to {}: errno {}", addr.sun_path, errno);
77+
}
5378
throw std::system_error(errno, std::generic_category());
5479
}
5580

56-
res = ::chmod(sv.data(), 0777); // NOLINT
57-
if (res == -1) {
58-
SPDLOG_ERROR(
59-
"Failed to chmod socket {}: errno {}", addr.sun_path, errno);
60-
throw std::system_error(errno, std::generic_category());
81+
if (!is_abstract) {
82+
res = ::chmod(sv.data(), 0777); // NOLINT
83+
if (res == -1) {
84+
SPDLOG_ERROR(
85+
"Failed to chmod socket {}: errno {}", addr.sun_path, errno);
86+
throw std::system_error(errno, std::generic_category());
87+
}
6188
}
6289

6390
static constexpr int backlog = 50;
6491
if (::listen(sock_.get(), backlog) == -1) {
6592
throw std::system_error(errno, std::generic_category());
6693
}
67-
SPDLOG_INFO("Started listening on {}", sv);
94+
95+
if (is_abstract) {
96+
SPDLOG_INFO("Started listening on abstract socket: {}", sv);
97+
} else {
98+
SPDLOG_INFO("Started listening on {}", sv);
99+
}
68100
}
69101

70102
void acceptor::set_accept_timeout(std::chrono::seconds timeout)

0 commit comments

Comments
 (0)