Skip to content

Commit

Permalink
feat(walredo): use posix_spawn by moving close_fd() work to walredo C…
Browse files Browse the repository at this point in the history
… code

The rust stdlib uses the efficient `posix_spawn` by default.
However, before this PR, pageserver used `pre_exec()` in our
`close_fds()` ext trait.

This PR moves the work that `close_fds()` did to the walredo C code.
I verified manually that we're now forking out the walredo process using
`posix_spawn`.

refs #6565
  • Loading branch information
problame committed Feb 1, 2024
1 parent fa52cd5 commit b524d37
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 58 deletions.
11 changes: 0 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ camino = "1.1.6"
cfg-if = "1.0.0"
chrono = { version = "0.4", default-features = false, features = ["clock"] }
clap = { version = "4.0", features = ["derive"] }
close_fds = "0.3.2"
comfy-table = "6.1"
const_format = "0.2"
crc32c = "0.6"
Expand Down
1 change: 0 additions & 1 deletion pageserver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ camino.workspace = true
camino-tempfile.workspace = true
chrono = { workspace = true, features = ["serde"] }
clap = { workspace = true, features = ["string"] }
close_fds.workspace = true
const_format.workspace = true
consumption_metrics.workspace = true
crc32c.workspace = true
Expand Down
53 changes: 8 additions & 45 deletions pageserver/src/walredo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use std::io;
use std::io::prelude::*;
use std::ops::{Deref, DerefMut};
use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::CommandExt;
use std::process::Stdio;
use std::process::{Child, ChildStdin, ChildStdout, Command};
use std::sync::{Arc, Mutex, MutexGuard, RwLock};
Expand Down Expand Up @@ -607,40 +606,6 @@ impl PostgresRedoManager {
}
}

///
/// Command with ability not to give all file descriptors to child process
///
trait CloseFileDescriptors: CommandExt {
///
/// Close file descriptors (other than stdin, stdout, stderr) in child process
///
fn close_fds(&mut self) -> &mut Command;
}

impl<C: CommandExt> CloseFileDescriptors for C {
fn close_fds(&mut self) -> &mut Command {
// SAFETY: Code executed inside pre_exec should have async-signal-safety,
// which means it should be safe to execute inside a signal handler.
// The precise meaning depends on platform. See `man signal-safety`
// for the linux definition.
//
// The set_fds_cloexec_threadsafe function is documented to be
// async-signal-safe.
//
// Aside from this function, the rest of the code is re-entrant and
// doesn't make any syscalls. We're just passing constants.
//
// NOTE: It's easy to indirectly cause a malloc or lock a mutex,
// which is not async-signal-safe. Be careful.
unsafe {
self.pre_exec(move || {
close_fds::set_fds_cloexec_threadsafe(3, &[]);
Ok(())
})
}
}
}

struct WalRedoProcess {
#[allow(dead_code)]
conf: &'static PageServerConf,
Expand Down Expand Up @@ -676,16 +641,14 @@ impl WalRedoProcess {
.env_clear()
.env("LD_LIBRARY_PATH", &pg_lib_dir_path)
.env("DYLD_LIBRARY_PATH", &pg_lib_dir_path)
// The redo process is not trusted, and runs in seccomp mode that
// doesn't allow it to open any files. We have to also make sure it
// doesn't inherit any file descriptors from the pageserver, that
// would allow an attacker to read any files that happen to be open
// in the pageserver.
//
// The Rust standard library makes sure to mark any file descriptors with
// as close-on-exec by default, but that's not enough, since we use
// libraries that directly call libc open without setting that flag.
.close_fds()
// NB: The redo process is not trusted after we sent it the first
// walredo work. Before that, it is trusted. Specifically, we trust
// it to
// 1. close all file descriptors except stdin, stdout, stderr because
// pageserver might not be 100% diligent in setting FD_CLOEXEC on all
// the files it opens, and
// 2. to use seccomp to sandbox itself before processing the first
// walredo request.
.spawn_no_leak_child(tenant_shard_id)
.context("spawn process")?;
WAL_REDO_PROCESS_COUNTERS.started.inc();
Expand Down
28 changes: 28 additions & 0 deletions pgxn/neon_walredo/walredoproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,37 @@ static XLogReaderState *reader_state;
#define TRACE DEBUG5

#ifdef HAVE_LIBSECCOMP


/*
* https://man7.org/linux/man-pages/man2/close_range.2.html
*
* The `close_range` syscall is available as of Linux 5.9.
*
* The `close_range` libc wrapper is only available in glibc >= 2.34.
* Debian Bullseye ships a libc package based on glibc 2.31.
* => write the wrapper ourselves, using the syscall number from the kernel headers.
*
* If the Linux uAPI headers don't define the system call number,
* fail the build deliberately rather than ifdef'ing it to ENOSYS.
* We prefer a compile time over a runtime error for walredo.
*/
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
int close_range(unsigned int start_fd, unsigned int count, unsigned int flags) {
return syscall(__NR_close_range, start_fd, count, flags);
}

static void
enter_seccomp_mode(void)
{
/* https://man7.org/linux/man-pages/man2/close_range.2.html */
int err;
if (err = close_range(3, ~0U, 0)) {
ereport(FATAL, (errcode(ERRCODE_SYSTEM_ERROR), errmsg("seccomp: could not close files >= fd 3")));
}

PgSeccompRule syscalls[] =
{
/* Hard requirements */
Expand Down

0 comments on commit b524d37

Please sign in to comment.