Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions datadog-sidecar/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub struct Config {
/// Socket/pipe buffer size for IPC connections (bytes).
/// 0 means use the platform default.
pub pipe_buffer_size: usize,
#[cfg(target_os = "linux")]
pub spawn_without_trampoline: bool,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -259,6 +261,8 @@ impl FromEnv {
appsec_config: Self::appsec_config(),
max_memory: Self::max_memory(),
pipe_buffer_size: Self::pipe_buffer_size(),
#[cfg(target_os = "linux")]
spawn_without_trampoline: false,
}
}

Expand Down
27 changes: 27 additions & 0 deletions datadog-sidecar/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
use anyhow::Context;
#[cfg(unix)]
use libdd_crashtracker;
#[cfg(target_os = "linux")]
use spawn_worker::read_pt_interp_self;
use spawn_worker::{entrypoint, Stdio};
use std::fs::File;
use std::future::Future;
Expand Down Expand Up @@ -221,6 +223,11 @@ pub fn daemonize(listener: IpcServer, mut cfg: Config) -> anyhow::Result<()> {
#[allow(unused_unsafe)] // the unix method is unsafe
let mut spawn_cfg = unsafe { spawn_worker::SpawnWorker::new() };

#[cfg(target_os = "linux")]
if cfg.spawn_without_trampoline && read_pt_interp_self().is_some() {
spawn_cfg.spawn_method(spawn_worker::SpawnMethod::Direct);
}

spawn_cfg.target(entrypoint!(ddog_daemon_entry_point));

match cfg.log_method {
Expand Down Expand Up @@ -256,6 +263,26 @@ pub fn daemonize(listener: IpcServer, mut cfg: Config) -> anyhow::Result<()> {
}
spawn_cfg.append_env("LSAN_OPTIONS", "detect_leaks=0");

// In ASAN builds the sidecar is the "main object" when exec'd directly by
// ld.so, so libclang_rt.asan lands behind libc in the link map. ASAN
// would otherwise abort with "does not come first in initial library list."
// set_env replaces any inherited ASAN_OPTIONS so getenv in the child finds
// our value first.
#[cfg(target_os = "linux")]
{
let asan_init =
unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"__asan_init".as_ptr() as *const _) };
if !asan_init.is_null() {
let existing = std::env::var("ASAN_OPTIONS").unwrap_or_default();
let asan_opts = if existing.is_empty() {
"verify_asan_link_order=0".to_owned()
} else {
format!("{}:verify_asan_link_order=0", existing)
};
spawn_cfg.set_env("ASAN_OPTIONS", asan_opts);
}
}

setup_daemon_process(listener, &mut spawn_cfg)?;

let mut lib_deps = cfg.library_dependencies;
Expand Down
54 changes: 35 additions & 19 deletions datadog-sidecar/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ pub extern "C" fn ddog_daemon_entry_point(trampoline_data: &TrampolineData) {
let _ = prctl::set_name("dd-ipc-helper");

#[cfg(target_os = "linux")]
if let Err(e) = init_crashtracker(trampoline_data.dependency_paths) {
if let Err(e) = init_crashtracker(if trampoline_data.argc > 0 {
Some(trampoline_data.dependency_paths)
} else {
None
}) {
warn!("Failed to initialize crashtracker: {e}");
}

Expand Down Expand Up @@ -214,32 +218,44 @@ fn shutdown_appsec() -> bool {
}

#[cfg(target_os = "linux")]
fn init_crashtracker(dependency_paths: *const *const libc::c_char) -> anyhow::Result<()> {
fn init_crashtracker(dependency_paths: Option<*const *const libc::c_char>) -> anyhow::Result<()> {
let entrypoint = entrypoint!(ddog_crashtracker_entry_point);
let entrypoint_path = match unsafe { get_dl_path_raw(entrypoint.ptr as *const libc::c_void) } {
(Some(path), _) => path,
_ => anyhow::bail!("Failed to find crashtracker entrypoint"),
};

let mut receiver_args = vec![
"crashtracker_receiver".to_string(),
"".to_string(),
entrypoint_path.into_string()?,
];

unsafe {
let mut descriptors = dependency_paths;
if !descriptors.is_null() {
loop {
if (*descriptors).is_null() {
break;
let entrypoint_path_str = entrypoint_path.into_string()?;

let mut receiver_args = vec!["crashtracker_receiver".to_string()];
let mut receiver_env = vec![];
let entrypoint_name = entrypoint.symbol_name.into_string()?;

if let Some(dependency_paths) = dependency_paths {
receiver_args.push("".to_string());
receiver_args.push(entrypoint_path_str.clone());
unsafe {
let mut descriptors = dependency_paths;
if !descriptors.is_null() {
loop {
if (*descriptors).is_null() {
break;
}
receiver_args.push(CStr::from_ptr(*descriptors).to_string_lossy().into_owned());
descriptors = descriptors.add(1);
}
receiver_args.push(CStr::from_ptr(*descriptors).to_string_lossy().into_owned());
descriptors = descriptors.add(1);
}
}
receiver_args.push(entrypoint_name);
} else {
// direct mode: ld.so uses argv[1] as the library to exec
receiver_args.push(entrypoint_path_str.clone());
receiver_env.push(("_DD_SIDECAR_DIRECT_EXEC".to_string(), entrypoint_name));
if let Ok(env) = std::env::var("_DD_SIDECAR_PATH_DEPS") {
if !env.is_empty() {
receiver_env.push(("_DD_SIDECAR_PATH_DEPS".to_string(), env));
}
}
}
receiver_args.push(entrypoint.symbol_name.into_string()?);

let output = match &Config::get().log_method {
LogMethod::Stdout => Some(format!("/proc/{}/fd/1", unsafe { libc::getpid() })),
Expand Down Expand Up @@ -269,7 +285,7 @@ fn init_crashtracker(dependency_paths: *const *const libc::c_char) -> anyhow::Re
config_builder.build()?,
CrashtrackerReceiverConfig::new(
receiver_args,
vec![],
receiver_env,
format!("/proc/{}/exe", unsafe { libc::getpid() }),
output,
None,
Expand Down
24 changes: 18 additions & 6 deletions spawn_worker/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@
pub use cc_utils::cc;

fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();

// Compile the ELF entry point for the shared library (direct exec by ld.so).
if target_os == "linux" {
let mut builder = cc::Build::new();
builder
.file("src/direct_entry.c")
.warnings(true)
.flag("-g")
.emit_rerun_if_env_changed(true)
.compile("ddog_spawn_direct_entry");
// Note, users of direct mode have to add to their build flags:
// -Wl,-e,ddog_spawn_direct_entry
}

let mut builder = cc_utils::ImprovedBuild::new();
builder
.file("src/trampoline.c")
Expand All @@ -13,7 +28,7 @@ fn main() {
.warnings_into_errors(true)
.emit_rerun_if_env_changed(true);

if !cfg!(target_os = "windows") {
if target_os != "windows" {
builder.link_dynamically("dl");
if cfg!(target_os = "linux") {
builder.flag("-Wl,--no-as-needed");
Expand All @@ -28,7 +43,7 @@ fn main() {

builder.try_compile_executable("trampoline.bin").unwrap();

if !cfg!(target_os = "windows") {
if target_os != "windows" {
cc_utils::ImprovedBuild::new()
.file("src/ld_preload_trampoline.c")
.link_dynamically("dl")
Expand All @@ -37,10 +52,7 @@ fn main() {
.emit_rerun_if_env_changed(true)
.try_compile_shared_lib("ld_preload_trampoline.shared_lib")
.unwrap();
}

#[cfg(target_os = "windows")]
{
} else {
cc_utils::ImprovedBuild::new()
.file("src/crashtracking_trampoline.cpp") // Path to your C++ file
.warnings(true)
Expand Down
184 changes: 184 additions & 0 deletions spawn_worker/src/direct_entry.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

// This file provides the ELF entry point (ddog_spawn_direct_entry) for the
// shared library that contains spawn_worker. When ld.so exec's that library
// directly, it calls this function rather than the trampoline.
// _DD_SIDECAR_DIRECT_EXEC must be set to the name of the symbol to call

#define _GNU_SOURCE
#include <alloca.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <elf.h>
#include <link.h>

static inline FILE *error_fd() {
char *log_env = getenv("DD_TRACE_LOG_FILE");
if (log_env) {
FILE *file = fopen(log_env, "a");
if (file) {
return file;
}
}
return stderr;
}

// All fields are zero here (no deps to clean up).
typedef struct {
int argc;
const char **argv;
const char **dependency_paths;
} trampoline_data_t;

// dlopen() each colon-separated path in _DD_SIDECAR_PATH_DEPS.
static void dlopen_path_deps(void) {
const char *deps = getenv("_DD_SIDECAR_PATH_DEPS");
if (!deps || !*deps) {
return;
}

// Work on a copy so we can NUL-terminate each token in place.
size_t len = strlen(deps);
char *buf = alloca(len + 1);
memcpy(buf, deps, len + 1);

char *path = buf;
while (*path) {
char *colon = strchr(path, ':');
if (colon) {
*colon = '\0';
}
if (*path) {
if (!dlopen(path, RTLD_LAZY | RTLD_GLOBAL)) {
fputs(dlerror(), error_fd());
_exit(11);
}
}
if (!colon) {
break;
}
path = colon + 1;
}
}

// Called by dl_iterate_phdr to run DT_INIT_ARRAY for our own library.
// Returns 1 when our library is found and processed (stopping iteration),
// 0 otherwise.
// Marked no_sanitize so it can run before ASAN's per-object init completes.
static int __attribute__((no_sanitize("address"), no_sanitize("undefined")))
run_init_array_cb(struct dl_phdr_info *info, size_t size, void *self_addr) {
(void)size;
for (int i = 0; i < info->dlpi_phnum; i++) {
if (info->dlpi_phdr[i].p_type != PT_LOAD) {
continue;
}

uintptr_t start = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
uintptr_t end = start + info->dlpi_phdr[i].p_memsz;
if ((uintptr_t)self_addr < start || (uintptr_t)self_addr >= end) {
continue;
}

// Found our library — locate DT_INIT_ARRAY in its DYNAMIC segment.
for (int j = 0; j < info->dlpi_phnum; j++) {
if (info->dlpi_phdr[j].p_type == PT_DYNAMIC) {
void (**arr)(void) = NULL;
size_t sz = 0;
for (ElfW(Dyn) *dyn = (ElfW(Dyn) *)(info->dlpi_addr + info->dlpi_phdr[j].p_vaddr); dyn->d_tag != DT_NULL; dyn++) {
if (dyn->d_tag == DT_INIT_ARRAY) {
arr = (void (**)(void))(info->dlpi_addr + dyn->d_un.d_ptr);
}
if (dyn->d_tag == DT_INIT_ARRAYSZ) {
sz = dyn->d_un.d_val;
}
}
if (arr) {
typedef void (*init_fn_t)(int, char **, char **);
extern char **environ;
for (size_t k = 0; k < sz / sizeof(void *); k++) {
if (arr[k] && (uintptr_t)arr[k] != (uintptr_t)-1) {
((init_fn_t)arr[k])(0, NULL, environ);
}
}
}
return 1;
}
}
}
return 0;
}

// Hidden (not static) so asm can reference it by name.
// @PLT in the call generates R_X86_64_PLT32 which old linkers accept for shared objects.
// 'used' prevents LTO from dropping it (it's only called from the naked asm below).
__attribute__((visibility("hidden"), used, noinline))
void ddog_spawn_direct_entry_body(void);

// Naked wrapper: ld.so JUMPs (not calls) to e_entry, so stack pointer alignment is
// unpredictable. We must align the stack BEFORE the C prologue runs: doing
// it inside the function body is too late because the prologue will already alter it.
__attribute__((visibility("default")))
#if defined(__x86_64__)
__attribute__((naked))
void ddog_spawn_direct_entry(void) {
__asm__ (
".cfi_undefined rip\n\t" /* no valid return address: bottom of stack */
"and $-16, %rsp\n\t"
"call ddog_spawn_direct_entry_body@PLT\n\t"
"ud2" /* unreachable: body calls _exit */
);
}
#elif defined(__aarch64__)
__attribute__((naked))
void ddog_spawn_direct_entry(void) {
__asm__ (
".cfi_undefined x30\n\t" /* no valid return address: bottom of stack */
"mov x9, sp\n\t"
"and x9, x9, #~15\n\t"
"mov sp, x9\n\t"
"bl ddog_spawn_direct_entry_body\n\t"
"brk #0" /* unreachable: body calls _exit */
);
}
#else
void ddog_spawn_direct_entry(void) {
ddog_spawn_direct_entry_body();
}
#endif

__attribute__((visibility("hidden"), used, noinline))
void ddog_spawn_direct_entry_body(void) {
// Run our own DT_INIT_ARRAY before any other code.
// ld.so skips DT_INIT_ARRAY for the main module in direct-exec mode, so
// ASAN's per-object global registration and other constructors never run
// unless we trigger them explicitly.
dl_iterate_phdr(run_init_array_cb, (void *)&ddog_spawn_direct_entry);

const char *symbol_name = getenv("_DD_SIDECAR_DIRECT_EXEC");
if (!symbol_name || !*symbol_name) {
fputs("_DD_SIDECAR_DIRECT_EXEC is not set. Aborting.", error_fd());
_exit(2);
}

// Load any path-dep libraries listed in _DD_SIDECAR_PATH_DEPS.
dlopen_path_deps();

// Call the requested symbol — avoids a link-time dependency on
// datadog-sidecar from spawn_worker.
typedef void (*entry_fn_t)(const trampoline_data_t *);
entry_fn_t entry = (entry_fn_t)dlsym(RTLD_DEFAULT, symbol_name);
if (entry) {
trampoline_data_t data = {0};
entry(&data);
} else {
fprintf(error_fd(), "fn was not found; missing %s in binary", symbol_name);
_exit(12);
}
_exit(0);
}
Loading
Loading