mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
bc4fd34fe2
This adds argv[0] for PLOG(FATAL) calls following a failed posix_spawn or execve call to make logs more useful. Bug: chromium:324982367 Change-Id: I179928ec9f791ce5b365b3444aa3bb667f4ec4b3 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/5315332 Reviewed-by: Mark Mentovai <mark@chromium.org> Commit-Queue: Peter Boström <pbos@chromium.org>
264 lines
8.6 KiB
C++
264 lines
8.6 KiB
C++
// Copyright 2017 The Crashpad Authors
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
#include "util/posix/spawn_subprocess.h"
|
||
|
||
#include <errno.h>
|
||
#include <spawn.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/wait.h>
|
||
#include <unistd.h>
|
||
|
||
#include "base/check.h"
|
||
#include "base/check_op.h"
|
||
#include "base/logging.h"
|
||
#include "base/posix/eintr_wrapper.h"
|
||
#include "base/strings/stringprintf.h"
|
||
#include "build/build_config.h"
|
||
#include "util/posix/close_multiple.h"
|
||
|
||
#if BUILDFLAG(IS_ANDROID)
|
||
#include <android/api-level.h>
|
||
#endif
|
||
|
||
extern char** environ;
|
||
|
||
namespace crashpad {
|
||
|
||
namespace {
|
||
|
||
#if BUILDFLAG(IS_APPLE)
|
||
|
||
class PosixSpawnAttr {
|
||
public:
|
||
PosixSpawnAttr() {
|
||
PCHECK((errno = posix_spawnattr_init(&attr_)) == 0)
|
||
<< "posix_spawnattr_init";
|
||
}
|
||
|
||
PosixSpawnAttr(const PosixSpawnAttr&) = delete;
|
||
PosixSpawnAttr& operator=(const PosixSpawnAttr&) = delete;
|
||
|
||
~PosixSpawnAttr() {
|
||
PCHECK((errno = posix_spawnattr_destroy(&attr_)) == 0)
|
||
<< "posix_spawnattr_destroy";
|
||
}
|
||
|
||
void SetFlags(short flags) {
|
||
PCHECK((errno = posix_spawnattr_setflags(&attr_, flags)) == 0)
|
||
<< "posix_spawnattr_setflags";
|
||
}
|
||
|
||
const posix_spawnattr_t* Get() const { return &attr_; }
|
||
|
||
private:
|
||
posix_spawnattr_t attr_;
|
||
};
|
||
|
||
class PosixSpawnFileActions {
|
||
public:
|
||
PosixSpawnFileActions() {
|
||
PCHECK((errno = posix_spawn_file_actions_init(&file_actions_)) == 0)
|
||
<< "posix_spawn_file_actions_init";
|
||
}
|
||
|
||
PosixSpawnFileActions(const PosixSpawnFileActions&) = delete;
|
||
PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;
|
||
|
||
~PosixSpawnFileActions() {
|
||
PCHECK((errno = posix_spawn_file_actions_destroy(&file_actions_)) == 0)
|
||
<< "posix_spawn_file_actions_destroy";
|
||
}
|
||
|
||
void AddInheritedFileDescriptor(int fd) {
|
||
PCHECK((errno = posix_spawn_file_actions_addinherit_np(&file_actions_,
|
||
fd)) == 0)
|
||
<< "posix_spawn_file_actions_addinherit_np";
|
||
}
|
||
|
||
const posix_spawn_file_actions_t* Get() const { return &file_actions_; }
|
||
|
||
private:
|
||
posix_spawn_file_actions_t file_actions_;
|
||
};
|
||
|
||
#endif
|
||
|
||
} // namespace
|
||
|
||
bool SpawnSubprocess(const std::vector<std::string>& argv,
|
||
const std::vector<std::string>* envp,
|
||
int preserve_fd,
|
||
bool use_path,
|
||
void (*child_function)()) {
|
||
// argv_c contains const char* pointers and is terminated by nullptr. This is
|
||
// suitable for passing to posix_spawn*() and execv*(). Although argv_c is not
|
||
// used in the parent process, it must be built in the parent process because
|
||
// it’s unsafe to do so in the child or grandchild process.
|
||
std::vector<const char*> argv_c;
|
||
argv_c.reserve(argv.size() + 1);
|
||
for (const std::string& argument : argv) {
|
||
argv_c.push_back(argument.c_str());
|
||
}
|
||
argv_c.push_back(nullptr);
|
||
|
||
std::vector<const char*> envp_c;
|
||
if (envp) {
|
||
envp_c.reserve(envp->size() + 1);
|
||
for (const std::string& variable : *envp) {
|
||
envp_c.push_back(variable.c_str());
|
||
}
|
||
envp_c.push_back(nullptr);
|
||
}
|
||
|
||
// The three processes involved are parent, child, and grandchild. The child
|
||
// exits immediately after spawning the grandchild, so the grandchild becomes
|
||
// an orphan and its parent process ID becomes 1. This relieves the parent and
|
||
// child of the responsibility to reap the grandchild with waitpid() or
|
||
// similar. The grandchild is expected to outlive the parent process, so the
|
||
// parent shouldn’t be concerned with reaping it. This approach means that
|
||
// accidental early termination of the handler process will not result in a
|
||
// zombie process.
|
||
pid_t pid = fork();
|
||
if (pid < 0) {
|
||
PLOG(ERROR) << "fork";
|
||
return false;
|
||
}
|
||
|
||
if (pid == 0) {
|
||
// Child process.
|
||
|
||
if (child_function) {
|
||
child_function();
|
||
}
|
||
|
||
// Call setsid(), creating a new process group and a new session, both led
|
||
// by this process. The new process group has no controlling terminal. This
|
||
// disconnects it from signals generated by the parent process’ terminal.
|
||
//
|
||
// setsid() is done in the child instead of the grandchild so that the
|
||
// grandchild will not be a session leader. If it were a session leader, an
|
||
// accidental open() of a terminal device without O_NOCTTY would make that
|
||
// terminal the controlling terminal.
|
||
//
|
||
// It’s not desirable for the grandchild to have a controlling terminal. The
|
||
// grandchild manages its own lifetime, such as by monitoring clients on its
|
||
// own and exiting when it loses all clients and when it deems it
|
||
// appropraite to do so. It may serve clients in different process groups or
|
||
// sessions than its original client, and receiving signals intended for its
|
||
// original client’s process group could be harmful in that case.
|
||
PCHECK(setsid() != -1) << "setsid";
|
||
|
||
// &argv_c[0] is a pointer to a pointer to const char data, but because of
|
||
// how C (not C++) works, posix_spawn*() and execv*() want a pointer to
|
||
// a const pointer to char data. They modify neither the data nor the
|
||
// pointers, so the const_cast is safe.
|
||
char* const* argv_for_spawn = const_cast<char* const*>(argv_c.data());
|
||
|
||
// This cast is safe for the same reason that the argv_for_spawn cast is.
|
||
char* const* envp_for_spawn =
|
||
envp ? const_cast<char* const*>(envp_c.data()) : environ;
|
||
|
||
#if BUILDFLAG(IS_ANDROID) && __ANDROID_API__ < 28
|
||
pid = fork();
|
||
if (pid < 0) {
|
||
PLOG(FATAL) << "fork";
|
||
}
|
||
|
||
if (pid > 0) {
|
||
// Child process.
|
||
|
||
// _exit() instead of exit(), because fork() was called.
|
||
_exit(EXIT_SUCCESS);
|
||
}
|
||
|
||
// Grandchild process.
|
||
|
||
CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
|
||
|
||
auto execve_fp = use_path ? execvpe : execve;
|
||
execve_fp(argv_for_spawn[0], argv_for_spawn, envp_for_spawn);
|
||
PLOG(FATAL) << (use_path ? "execvpe" : "execve") << " "
|
||
<< argv_for_spawn[0];
|
||
#else
|
||
#if BUILDFLAG(IS_APPLE)
|
||
PosixSpawnAttr attr;
|
||
attr.SetFlags(POSIX_SPAWN_CLOEXEC_DEFAULT);
|
||
|
||
PosixSpawnFileActions file_actions;
|
||
for (int fd = 0; fd <= STDERR_FILENO; ++fd) {
|
||
file_actions.AddInheritedFileDescriptor(fd);
|
||
}
|
||
file_actions.AddInheritedFileDescriptor(preserve_fd);
|
||
|
||
const posix_spawnattr_t* attr_p = attr.Get();
|
||
const posix_spawn_file_actions_t* file_actions_p = file_actions.Get();
|
||
#else
|
||
CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
|
||
|
||
const posix_spawnattr_t* attr_p = nullptr;
|
||
const posix_spawn_file_actions_t* file_actions_p = nullptr;
|
||
#endif
|
||
|
||
auto posix_spawn_fp = use_path ? posix_spawnp : posix_spawn;
|
||
if ((errno = posix_spawn_fp(nullptr,
|
||
argv_for_spawn[0],
|
||
file_actions_p,
|
||
attr_p,
|
||
argv_for_spawn,
|
||
envp_for_spawn)) != 0) {
|
||
PLOG(FATAL) << (use_path ? "posix_spawnp" : "posix_spawn") << " "
|
||
<< argv_for_spawn[0];
|
||
}
|
||
|
||
// _exit() instead of exit(), because fork() was called.
|
||
_exit(EXIT_SUCCESS);
|
||
#endif
|
||
}
|
||
|
||
// waitpid() for the child, so that it does not become a zombie process. The
|
||
// child normally exits quickly.
|
||
//
|
||
// Failures from this point on may result in the accumulation of a zombie, but
|
||
// should not be considered fatal. Log only warnings, but don’t treat these
|
||
// failures as a failure of the function overall.
|
||
int status;
|
||
pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
|
||
if (wait_pid == -1) {
|
||
PLOG(WARNING) << "waitpid";
|
||
return true;
|
||
}
|
||
DCHECK_EQ(wait_pid, pid);
|
||
|
||
if (WIFSIGNALED(status)) {
|
||
int sig = WTERMSIG(status);
|
||
LOG(WARNING) << base::StringPrintf(
|
||
"intermediate process terminated by signal %d (%s)%s",
|
||
sig,
|
||
strsignal(sig),
|
||
WCOREDUMP(status) ? " (core dumped)" : "");
|
||
} else if (!WIFEXITED(status)) {
|
||
LOG(WARNING) << base::StringPrintf(
|
||
"intermediate process: unknown termination 0x%x", status);
|
||
} else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
|
||
LOG(WARNING) << "intermediate process exited with code "
|
||
<< WEXITSTATUS(status);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
} // namespace crashpad
|