Skip to content

Commit 50ddcd3

Browse files
authored
Add --stirling_uprobe_opt_out cli flag to allow opting binaries from uprobe attachment (#1971)
Summary: Add `--stirling_uprobe_opt_out` cli flag to allow opting binaries from uprobe attachment See #1970 for the motivation of this change. Relevant Issues: #1970 Type of change: /kind feature Test Plan: Verified the following scenarios - [x] BPF tests pass on all kernels -- see [this build-and-test run](https://github.com/pixie-io/pixie/actions/runs/10063985263) for the results. - [x] Dynamically linked OpenSSL application is skipped from uprobe attachment ``` # Run //src/stirling/source_connectors/socket_tracer/testing/containers:nginx_alpine_openssl_3_0_8_image # Run stirling_wrapper with --stirling_uprobe_opt_out=nginx,non_matching --vmodule=uprobe_manager=1 [ .. ] I20240723 17:40:54.208112 739524 uprobe_manager.cc:584] binary filename '/proc/738767/root/usr/sbin/nginx' contained in uprobe opt out list, skipping. I20240723 17:40:54.209451 739524 uprobe_manager.cc:584] binary filename '/proc/738731/root/usr/sbin/nginx' contained in uprobe opt out list, skipping. ``` - [x] Statically linked BoringSSL application is skipped from uprobe attachment ``` # Run //src/stirling/source_connectors/socket_tracer/testing/containers/bssl:bssl_image # Run stirling_wrapper with --stirling_uprobe_opt_out=bssl,non_matching --vmodule=uprobe_manager=1 [ .. ] I20240723 17:51:14.265595 742187 uprobe_manager.cc:584] binary filename '/proc/741971/root/app/bssl.runfiles/boringssl/bssl' contained in uprobe opt out list, skipping. ``` - [x] Nodejs application is skipped from uprobe attachment ``` # Run //src/stirling/source_connectors/socket_tracer/testing/containers:node_14_18_1_alpine_image # Run stirling_wrapper with --stirling_uprobe_opt_out=https_server.js,non_matching --vmodule=uprobe_manager=1 [ .. ] I20240723 16:50:01.197883 725584 uprobe_manager.cc:486] binary filename 'https_server.js' contained in uprobe opt out list, skipping. ``` - [x] Go application attachment is skipped with `--stirling_uprobe_opt_out=https_server.js,non_matching` ``` # Run //src/stirling/testing/demo_apps/go_https/server:golang_1_21_https_server # Run stirling_wrapper with --stirling_uprobe_opt_out=golang_1_21_server_binary,non_matching --vmodule=uprobe_manager=1 [ .. ] I20240723 17:29:54.166582 736461 uprobe_manager.cc:635] binary filename '/golang_1_21_server_binary' contained in uprobe opt out list, skipping. ``` Changelog Message: Add mechanism for opting out specific binaries from uprobe attachment --------- Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent b01f8ae commit 50ddcd3

File tree

5 files changed

+66
-9
lines changed

5 files changed

+66
-9
lines changed

src/stirling/source_connectors/socket_tracer/uprobe_manager.cc

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ DEFINE_double(stirling_rescan_exp_backoff_factor, 2.0,
5252
"Exponential backoff factor used in decided how often to rescan binaries for "
5353
"dynamically loaded libraries");
5454

55+
DEFINE_string(
56+
stirling_uprobe_opt_out, "",
57+
"Comma separated list of binary filenames that should be excluded from uprobe attachment."
58+
"For a binary at path /path/to/binary, the filename would be binary");
59+
5560
namespace px {
5661
namespace stirling {
5762

@@ -62,8 +67,13 @@ using ::px::system::KernelVersion;
6267
using ::px::system::KernelVersionOrder;
6368
using ::px::system::ProcPidRootPath;
6469

70+
constexpr std::string_view kUprobeSkippedMessage =
71+
"binary filename '$0' contained in uprobe opt out list, skipping.";
72+
6573
UProbeManager::UProbeManager(bpf_tools::BCCWrapper* bcc) : bcc_(bcc) {
6674
proc_parser_ = std::make_unique<system::ProcParser>();
75+
auto opt_out_list = absl::StrSplit(FLAGS_stirling_uprobe_opt_out, ",", absl::SkipWhitespace());
76+
uprobe_opt_out_ = absl::flat_hash_set<std::string>(opt_out_list.begin(), opt_out_list.end());
6777
}
6878

6979
void UProbeManager::Init(bool disable_go_tls_tracing, bool enable_http2_tracing,
@@ -447,8 +457,8 @@ StatusOr<std::array<UProbeTmpl, 6>> UProbeManager::GetNodeOpensslUProbeTmpls(con
447457
return iter->second;
448458
}
449459

450-
StatusOr<int> UProbeManager::AttachOpenSSLUProbesOnStaticBinary(const uint32_t pid) {
451-
PX_ASSIGN_OR_RETURN(const std::filesystem::path proc_exe, proc_parser_->GetExePath(pid));
460+
StatusOr<int> UProbeManager::AttachOpenSSLUProbesOnStaticBinary(
461+
const uint32_t pid, const std::filesystem::path& proc_exe) {
452462
const auto host_proc_exe = ProcPidRootPath(pid, proc_exe);
453463

454464
PX_ASSIGN_OR_RETURN(auto elf_reader, ElfReader::Create(host_proc_exe));
@@ -467,13 +477,19 @@ StatusOr<int> UProbeManager::AttachOpenSSLUProbesOnStaticBinary(const uint32_t p
467477
return kOpenSSLUProbes.size();
468478
}
469479

470-
StatusOr<int> UProbeManager::AttachNodeJsOpenSSLUprobes(const uint32_t pid) {
471-
PX_ASSIGN_OR_RETURN(const std::filesystem::path proc_exe, proc_parser_->GetExePath(pid));
472-
480+
StatusOr<int> UProbeManager::AttachNodeJsOpenSSLUprobes(const uint32_t pid,
481+
const std::filesystem::path& proc_exe) {
473482
if (DetectApplication(proc_exe) != Application::kNode) {
474483
return 0;
475484
}
476485

486+
const std::string exe_cmdline = proc_parser_->GetPIDCmdline(pid);
487+
const auto node_application_filepath = GetNodeApplicationFilename(exe_cmdline);
488+
if (node_application_filepath.has_value() &&
489+
uprobe_opt_out_.contains(node_application_filepath.value())) {
490+
VLOG(1) << absl::Substitute(kUprobeSkippedMessage, node_application_filepath.value());
491+
return 0;
492+
}
477493
const auto host_proc_exe = ProcPidRootPath(pid, proc_exe);
478494

479495
const auto [_, inserted] = nodejs_binaries_.insert(host_proc_exe.string());
@@ -608,6 +624,13 @@ int UProbeManager::DeployOpenSSLUProbes(const absl::flat_hash_set<md::UPID>& pid
608624
continue;
609625
}
610626

627+
PX_ASSIGN_OR(const auto exe_path, proc_parser_->GetExePath(pid.pid()), continue);
628+
629+
if (uprobe_opt_out_.contains(exe_path.filename().string())) {
630+
VLOG(1) << absl::Substitute(kUprobeSkippedMessage, exe_path.string());
631+
continue;
632+
}
633+
611634
auto count_or = AttachOpenSSLUProbesOnDynamicLib(pid.pid());
612635
if (count_or.ok()) {
613636
uprobe_count += count_or.ValueOrDie();
@@ -622,7 +645,7 @@ int UProbeManager::DeployOpenSSLUProbes(const absl::flat_hash_set<md::UPID>& pid
622645
count_or.ToString());
623646
}
624647

625-
count_or = AttachNodeJsOpenSSLUprobes(pid.pid());
648+
count_or = AttachNodeJsOpenSSLUprobes(pid.pid(), exe_path);
626649
if (count_or.ok()) {
627650
uprobe_count += count_or.ValueOrDie();
628651
VLOG(1) << absl::Substitute(
@@ -640,7 +663,7 @@ int UProbeManager::DeployOpenSSLUProbes(const absl::flat_hash_set<md::UPID>& pid
640663

641664
// Attach uprobes to statically linked applications only if no other probes have been attached.
642665
if (FLAGS_stirling_trace_static_tls_binaries && count_or.ok() && count_or.ValueOrDie() == 0) {
643-
count_or = AttachOpenSSLUProbesOnStaticBinary(pid.pid());
666+
count_or = AttachOpenSSLUProbesOnStaticBinary(pid.pid(), exe_path);
644667

645668
if (count_or.ok() && count_or.ValueOrDie() > 0) {
646669
uprobe_count += count_or.ValueOrDie();
@@ -817,6 +840,12 @@ int UProbeManager::DeployGoUProbes(const absl::flat_hash_set<md::UPID>& pids) {
817840
static int32_t kPID = getpid();
818841

819842
for (const auto& [binary, pid_vec] : ConvertPIDsListToMap(pids)) {
843+
std::filesystem::path binary_path(binary);
844+
auto binary_filepath = binary_path.filename().string();
845+
if (uprobe_opt_out_.contains(binary_filepath)) {
846+
VLOG(1) << absl::Substitute(kUprobeSkippedMessage, binary_filepath);
847+
continue;
848+
}
820849
// Don't bother rescanning binaries that have been scanned before to avoid unnecessary work.
821850
if (!scanned_binaries_.insert(binary).second) {
822851
continue;

src/stirling/source_connectors/socket_tracer/uprobe_manager.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ class UProbeManager {
538538
* @return The number of uprobes deployed. It is not an error if the binary
539539
* does not use OpenSSL; instead the return value will be zero.
540540
*/
541-
StatusOr<int> AttachNodeJsOpenSSLUprobes(uint32_t pid);
541+
StatusOr<int> AttachNodeJsOpenSSLUprobes(uint32_t pid, const std::filesystem::path& binary_path);
542542

543543
/**
544544
* Attaches the required probes for TLS tracing to the specified PID if the binary is
@@ -551,7 +551,8 @@ class UProbeManager {
551551
* @return The number of uprobes deployed. It is not an error if the binary
552552
* does not contain the necessary symbols to probe; instead the return value will be zero.
553553
*/
554-
StatusOr<int> AttachOpenSSLUProbesOnStaticBinary(uint32_t pid);
554+
StatusOr<int> AttachOpenSSLUProbesOnStaticBinary(uint32_t pid,
555+
const std::filesystem::path& binary_path);
555556

556557
/**
557558
* Calls BCCWrapper.AttachUProbe() with a probe template and log any errors to the probe status
@@ -628,6 +629,7 @@ class UProbeManager {
628629
// Without clean-up, these could consume more-and-more memory.
629630
absl::flat_hash_set<std::string> openssl_probed_binaries_;
630631
absl::flat_hash_set<std::string> scanned_binaries_;
632+
absl::flat_hash_set<std::string> uprobe_opt_out_;
631633
absl::flat_hash_set<std::string> go_probed_binaries_;
632634
absl::flat_hash_set<std::string> go_http2_probed_binaries_;
633635
absl::flat_hash_set<std::string> go_tls_probed_binaries_;

src/stirling/utils/detect_application.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ Application DetectApplication(const std::filesystem::path& exe) {
4949
return Application::kUnknown;
5050
}
5151

52+
// This method returns the main nodejs application file from a command line. See the following
53+
// examples below:
54+
//
55+
// "node /usr/bin/test.js" -> "test.js"
56+
// "node --node-memory-debug /usr/bin/test.js" -> "test.js"
57+
// "node /usr/bin/test" -> std::nullopt
58+
std::optional<std::string> GetNodeApplicationFilename(std::string_view cmdline) {
59+
std::vector<std::string> cmdline_parts = absl::StrSplit(cmdline, ' ');
60+
for (const auto& part : cmdline_parts) {
61+
if (absl::EndsWith(part, ".js")) {
62+
std::filesystem::path path(part);
63+
return path.filename();
64+
}
65+
}
66+
return {};
67+
}
68+
5269
bool operator<(const SemVer& lhs, const SemVer& rhs) {
5370
std::vector<int> lhs_vec = {lhs.major, lhs.minor, lhs.patch};
5471
std::vector<int> rhs_vec = {rhs.major, rhs.minor, rhs.patch};

src/stirling/utils/detect_application.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ enum class Application {
3636
// Returns the application of the input executable.
3737
Application DetectApplication(const std::filesystem::path& exe);
3838

39+
// Returns the filename of a node application from the command line.
40+
std::optional<std::string> GetNodeApplicationFilename(std::string_view cmdline);
41+
3942
// Describes a semantic versioning number.
4043
struct SemVer {
4144
int major = 0;

src/stirling/utils/detect_application_test.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ TEST(DetectApplicationTest, ResultsAreAsExpected) {
3434
EXPECT_EQ(Application::kNode, DetectApplication("/usr/bin/nodejs"));
3535
}
3636

37+
TEST(GetNodeApplicatFilenameTest, ResultsAreAsExpected) {
38+
EXPECT_EQ(GetNodeApplicationFilename("node /usr/bin/test.js"), "test.js");
39+
EXPECT_EQ(GetNodeApplicationFilename("node --node-memory-debug /usr/bin/test.js"), "test.js");
40+
EXPECT_FALSE(GetNodeApplicationFilename("node /usr/bin/test").has_value());
41+
}
42+
3743
TEST(GetSemVerTest, AsExpected) {
3844
ASSERT_OK_AND_ASSIGN(SemVer sem_ver, GetSemVer("v1.12.13-test", true));
3945
EXPECT_EQ(sem_ver.major, 1);

0 commit comments

Comments
 (0)