Skip to content

Commit f0b4549

Browse files
committed
fix(sidecar): use fchown via SO_PEERCRED to grant cross-user SHM access
In PHP-FPM thread mode the master process runs as root and spawns worker processes as www-data. Named POSIX SHM objects were created with 0600 (owner-only), so workers could not open them for writing. The correct fix is to fchown() the SHM to the worker's UID after creation. The worker UID is obtained via SO_PEERCRED (peer_cred()) on the first accepted Unix socket connection in the thread listener, before the SHM lazy-lock is initialized. Changes: - Replace set_shm_open_mode/SHM_OPEN_MODE with set_shm_owner_uid/SHM_OWNER_UID in both mem_handle.rs and mem_handle_macos.rs - Call fchown(fd, worker_uid, None) in NamedShmHandle::create_mode() when SHM_OWNER_UID is set; restore default mode to S_IWUSR|S_IRUSR (0600) - Add nix "user" feature to datadog-ipc for fchown/Uid support - Add init_shm_eagerly field to MainLoopConfig (default true); thread mode sets it false to defer SHM initialization to first connection - In accept_socket_loop_thread: use FIRST_CONNECTION_INIT OnceLock to call set_shm_owner_uid(peer_uid) then init SHM_LIMITER exactly once on first worker connection - Remove ddog_sidecar_set_shm_open_mode FFI function (no longer needed)
1 parent 1897357 commit f0b4549

7 files changed

Lines changed: 62 additions & 25 deletions

File tree

datadog-ipc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ tracing-subscriber = { version = "0.3.22" }
4444
spawn_worker = { path = "../spawn_worker" }
4545

4646
[target.'cfg(not(windows))'.dependencies]
47-
nix = { version = "0.29", features = ["fs", "mman", "process", "poll", "socket"] }
47+
nix = { version = "0.29", features = ["fs", "mman", "process", "poll", "socket", "user"] }
4848
sendfd = { version = "0.4", features = ["tokio"] }
4949
tokio = { version = "1.23", features = ["sync", "io-util", "signal"] }
5050

datadog-ipc/src/platform/unix/mem_handle.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ use nix::errno::Errno;
1010
use nix::fcntl::{open, OFlag};
1111
use nix::sys::mman::{self, mmap, munmap, MapFlags, ProtFlags};
1212
use nix::sys::stat::Mode;
13-
use nix::unistd::{ftruncate, mkdir, unlink};
13+
use nix::unistd::{fchown, ftruncate, mkdir, unlink, Uid};
1414
use nix::NixPath;
1515
use std::ffi::{CStr, CString};
1616
use std::fs::File;
1717
use std::io;
1818
use std::num::NonZeroUsize;
1919
use std::os::fd::AsFd;
2020
use std::os::unix::fs::MetadataExt;
21+
use std::os::unix::io::AsRawFd;
2122
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
2223

24+
// Sentinel value meaning "no owner UID override"
25+
const NO_OWNER_UID: u32 = u32::MAX;
26+
2327
fn fallback_path<P: ?Sized + NixPath>(name: &P) -> nix::Result<CString> {
2428
name.with_nix_path(|cstr| {
2529
let mut path = "/tmp/libdatadog".to_string().into_bytes();
@@ -95,14 +99,19 @@ pub(crate) fn munmap_handle<T: MemoryHandle>(mapped: &mut MappedMem<T>) {
9599

96100
static ANON_SHM_ID: AtomicI32 = AtomicI32::new(0);
97101

98-
static SHM_OPEN_MODE: AtomicU32 = AtomicU32::new(0o600);
102+
static SHM_OWNER_UID: AtomicU32 = AtomicU32::new(NO_OWNER_UID);
99103

100-
pub fn set_shm_open_mode(mode: u32) {
101-
SHM_OPEN_MODE.store(mode, Ordering::Relaxed);
104+
pub fn set_shm_owner_uid(uid: u32) {
105+
SHM_OWNER_UID.store(uid, Ordering::Relaxed);
102106
}
103107

104-
fn shm_open_mode() -> Mode {
105-
Mode::from_bits_truncate(SHM_OPEN_MODE.load(Ordering::Relaxed))
108+
fn shm_owner_uid() -> Option<u32> {
109+
let uid = SHM_OWNER_UID.load(Ordering::Relaxed);
110+
if uid == NO_OWNER_UID {
111+
None
112+
} else {
113+
Some(uid)
114+
}
106115
}
107116

108117
impl ShmHandle {
@@ -149,12 +158,15 @@ impl ShmHandle {
149158

150159
impl NamedShmHandle {
151160
pub fn create(path: CString, size: usize) -> io::Result<NamedShmHandle> {
152-
Self::create_mode(path, size, shm_open_mode())
161+
Self::create_mode(path, size, Mode::S_IWUSR | Mode::S_IRUSR)
153162
}
154163

155164
pub fn create_mode(path: CString, size: usize, mode: Mode) -> io::Result<NamedShmHandle> {
156165
let fd = shm_open(path.as_bytes(), OFlag::O_CREAT | OFlag::O_RDWR, mode)?;
157166
ftruncate(&fd, size as off_t)?;
167+
if let Some(uid) = shm_owner_uid() {
168+
let _ = fchown(fd.as_raw_fd(), Some(Uid::from_raw(uid)), None);
169+
}
158170
Self::new(fd, Some(path), size)
159171
}
160172

datadog-ipc/src/platform/unix/mem_handle_macos.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ use nix::errno::Errno;
99
use nix::fcntl::OFlag;
1010
use nix::sys::mman::{mmap, munmap, shm_open, shm_unlink, MapFlags, ProtFlags};
1111
use nix::sys::stat::Mode;
12-
use nix::unistd::ftruncate;
12+
use nix::unistd::{fchown, ftruncate, Uid};
1313
use std::ffi::{CStr, CString};
1414
use std::io;
1515
use std::num::NonZeroUsize;
1616
use std::os::fd::{AsFd, OwnedFd};
17+
use std::os::unix::io::AsRawFd;
1718
use std::sync::atomic::{AtomicI32, AtomicU32, AtomicUsize, Ordering};
1819

1920
const MAPPING_MAX_SIZE: usize = 1 << 17; // 128 MiB ought to be enough for everybody?
@@ -69,14 +70,21 @@ pub(crate) fn munmap_handle<T: MemoryHandle>(mapped: &MappedMem<T>) {
6970

7071
static ANON_SHM_ID: AtomicI32 = AtomicI32::new(0);
7172

72-
static SHM_OPEN_MODE: AtomicU32 = AtomicU32::new(0o600);
73+
const NO_OWNER_UID: u32 = u32::MAX;
7374

74-
pub fn set_shm_open_mode(mode: u32) {
75-
SHM_OPEN_MODE.store(mode, Ordering::Relaxed);
75+
static SHM_OWNER_UID: AtomicU32 = AtomicU32::new(NO_OWNER_UID);
76+
77+
pub fn set_shm_owner_uid(uid: u32) {
78+
SHM_OWNER_UID.store(uid, Ordering::Relaxed);
7679
}
7780

78-
fn shm_open_mode() -> Mode {
79-
Mode::from_bits_truncate(SHM_OPEN_MODE.load(Ordering::Relaxed) as u16)
81+
fn shm_owner_uid() -> Option<u32> {
82+
let uid = SHM_OWNER_UID.load(Ordering::Relaxed);
83+
if uid == NO_OWNER_UID {
84+
None
85+
} else {
86+
Some(uid)
87+
}
8088
}
8189

8290
impl ShmHandle {
@@ -110,7 +118,7 @@ fn path_slice(path: &CStr) -> &[u8] {
110118

111119
impl NamedShmHandle {
112120
pub fn create(path: CString, size: usize) -> io::Result<NamedShmHandle> {
113-
Self::create_mode(path, size, shm_open_mode())
121+
Self::create_mode(path, size, Mode::S_IWUSR | Mode::S_IRUSR)
114122
}
115123

116124
pub fn create_mode(path: CString, size: usize, mode: Mode) -> io::Result<NamedShmHandle> {
@@ -122,6 +130,9 @@ impl NamedShmHandle {
122130
truncate?;
123131
}
124132
}
133+
if let Some(uid) = shm_owner_uid() {
134+
let _ = fchown(fd.as_raw_fd(), Some(Uid::from_raw(uid)), None);
135+
}
125136
Self::new(fd, Some(path), size)
126137
}
127138

datadog-ipc/src/platform/unix/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ pub(crate) use mem_handle_macos::*;
1919
#[cfg(not(target_os = "macos"))]
2020
mod mem_handle;
2121
#[cfg(not(target_os = "macos"))]
22-
pub use mem_handle::set_shm_open_mode;
22+
pub use mem_handle::set_shm_owner_uid;
2323
#[cfg(not(target_os = "macos"))]
2424
pub(crate) use mem_handle::*;
2525
#[cfg(target_os = "macos")]
26-
pub use mem_handle_macos::set_shm_open_mode;
26+
pub use mem_handle_macos::set_shm_owner_uid;
2727

2828
#[no_mangle]
2929
#[cfg(polyfill_glibc_memfd)]

datadog-sidecar-ffi/src/lib.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,6 @@ pub extern "C" fn ddog_sidecar_connect_master(pid: i32) -> MaybeError {
323323
MaybeError::None
324324
}
325325

326-
#[no_mangle]
327-
#[cfg(unix)]
328-
pub extern "C" fn ddog_sidecar_set_shm_open_mode(mode: u32) {
329-
datadog_ipc::platform::set_shm_open_mode(mode);
330-
}
331-
332326
#[no_mangle]
333327
pub extern "C" fn ddog_sidecar_connect_worker(
334328
pid: i32,

datadog-sidecar/src/entry.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ pub struct MainLoopConfig {
3737
pub enable_ctrl_c_handler: bool,
3838
pub enable_crashtracker: bool,
3939
pub external_shutdown_rx: Option<oneshot::Receiver<()>>,
40+
/// Set to false in thread mode so the worker's UID can be obtained on the
41+
/// first connection and used to fchown the SHM.
42+
pub init_shm_eagerly: bool,
4043
}
4144

4245
impl Default for MainLoopConfig {
@@ -45,6 +48,7 @@ impl Default for MainLoopConfig {
4548
enable_ctrl_c_handler: true,
4649
enable_crashtracker: true,
4750
external_shutdown_rx: None,
51+
init_shm_eagerly: true,
4852
}
4953
}
5054
}
@@ -125,8 +129,9 @@ where
125129
});
126130
}
127131

128-
// Init. Early, before we start listening.
129-
drop(SHM_LIMITER.lock());
132+
if loop_config.init_shm_eagerly {
133+
drop(SHM_LIMITER.lock());
134+
}
130135

131136
let server = SidecarServer::default();
132137

datadog-sidecar/src/setup/thread_listener.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ use crate::setup::AbstractUnixSocketLiaison;
1616
use crate::setup::Liaison;
1717
#[cfg(not(target_os = "linux"))]
1818
use crate::setup::SharedDirLiaison;
19+
use crate::tracer::SHM_LIMITER;
1920
use datadog_ipc::transport::blocking::BlockingTransport;
2021

2122
static MASTER_LISTENER: OnceLock<Mutex<Option<MasterListener>>> = OnceLock::new();
2223

24+
/// Ensures first-connection SHM initialization runs exactly once across all threads.
25+
static FIRST_CONNECTION_INIT: OnceLock<()> = OnceLock::new();
26+
2327
pub struct MasterListener {
2428
shutdown_tx: Option<oneshot::Sender<()>>,
2529
thread_handle: Option<JoinHandle<()>>,
@@ -139,6 +143,15 @@ async fn accept_socket_loop_thread(
139143
match accept {
140144
Ok((socket, _)) => {
141145
info!("Accepted new worker connection");
146+
// On the first connection, get the worker's UID and
147+
// fchown the SHM to that UID so cross-user access works when
148+
// the master runs as root and workers run as a different user.
149+
FIRST_CONNECTION_INIT.get_or_init(|| {
150+
if let Ok(cred) = socket.peer_cred() {
151+
datadog_ipc::platform::set_shm_owner_uid(cred.uid());
152+
}
153+
drop(SHM_LIMITER.lock());
154+
});
142155
handler(socket);
143156
}
144157
Err(e) => {
@@ -179,6 +192,8 @@ fn run_listener(pid: u32, _config: Config, shutdown_rx: oneshot::Receiver<()>) -
179192
enable_ctrl_c_handler: false,
180193
enable_crashtracker: false,
181194
external_shutdown_rx: None,
195+
// Defer SHM init to first connection so we can fchown using the worker's UID.
196+
init_shm_eagerly: false,
182197
};
183198

184199
let runtime = tokio::runtime::Builder::new_current_thread()

0 commit comments

Comments
 (0)