OMS-Auditd-Plugin/ExecUtil.cpp

380 строки
9.6 KiB
C++

//
// Created by tad on 3/19/19.
//
#include "ExecUtil.h"
#include "Gate.h"
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <poll.h>
#include <stdexcept>
#include <sstream>
#include <unordered_map>
#include <cstring>
#include <pthread.h>
#include <functional>
void write_error(int reason, int err, int fd) {
uint32_t code = (static_cast<uint32_t>(reason) << 16) | static_cast<uint32_t>(err);
write(fd, &code, sizeof(code));
}
void Cmd::cleanup() {
if (_stdin > -1) {
close(_stdin);
_stdin = -1;
}
if (_stdout > -1) {
close(_stdout);
if (_stderr == _stdout) {
_stderr = -1;
}
_stdout = -1;
}
if (_stderr > -1) {
close(_stderr);
_stderr = -1;
}
_pid = 0;
_fail_reason = 0;
_errno = 0;
_stdin = -1;
_stdout = -1;
_stderr = -1;
_exitcode = -1;
_signal = -1;
}
int Cmd::Start() {
if (_pid > 0) {
return -EBUSY;
}
cleanup();
int sigpipe[2];
int inpipe[2] = {-1, -1};
int outpipe[2] = {-1, -1};
int errpipe[2] = {-1, -1};
int ret = 0;
ret = pipe2(sigpipe, O_CLOEXEC);
if (ret != 0) {
_fail_reason = FAILED_PIPE2;
_errno = errno;
return -errno;
}
if (STDIN_FLAGS(_flags) == PIPE_STDIN) {
ret = pipe(inpipe);
if (ret != 0) {
_fail_reason = FAILED_PIPE;
_errno = errno;
return -errno;
}
}
if (STDOUT_FLAGS(_flags) & PIPE_STDOUT) {
ret = pipe(outpipe);
if (ret != 0) {
_fail_reason = FAILED_PIPE;
_errno = errno;
return -errno;
}
}
if (STDERR_FLAGS(_flags) == PIPE_STDERR) {
ret = pipe(errpipe);
if (ret != 0) {
_fail_reason = FAILED_PIPE;
_errno = errno;
return -errno;
}
}
if ((_flags & COMBINE_OUTPUT) == COMBINE_OUTPUT) {
errpipe[0] = outpipe[0];
errpipe[1] = outpipe[1];
}
auto pid = fork();
if (pid < 0) {
_fail_reason = FAILED_FORK;
_errno = errno;
return -errno;
}
if (pid == 0) {
close(sigpipe[_PIPE_READ]);
char *args[_args.size()+2];
args[0] = new char[_path.size()+1];
_path.copy(args[0], _path.size());
args[0][_path.size()] = 0;
int idx = 1;
for(auto& arg: _args) {
args[idx] = new char[arg.size()+1];
arg.copy(args[idx], arg.size());
args[idx][arg.size()] = 0;
idx++;
}
args[idx] = nullptr;
if (inpipe[_PIPE_READ] != -1) {
ret = dup2(inpipe[_PIPE_READ], 0);
if (ret != 0) {
write_error(FAILED_DUP2, errno, sigpipe[_PIPE_WRITE]);
exit(1);
}
} else if (STDIN_FLAGS(_flags) == NULL_STDIN) {
int in = open("/dev/null", O_RDONLY);
if (in < 0) {
write_error(FAILED_OPEN, errno, sigpipe[_PIPE_WRITE]);
exit(1);
}
ret = dup2(in, 0);
if (ret != 0) {
write_error(FAILED_DUP2, errno, sigpipe[_PIPE_WRITE]);
exit(1);
}
}
if (outpipe[_PIPE_WRITE] != -1) {
ret = dup2(outpipe[_PIPE_WRITE], 1);
if (ret != 1) {
write_error(FAILED_DUP2, errno, sigpipe[_PIPE_WRITE]);
exit(1);
}
}
if (errpipe[_PIPE_WRITE] != -1) {
ret = dup2(errpipe[_PIPE_WRITE], 2);
if (ret != 2) {
write_error(FAILED_DUP2, errno, sigpipe[_PIPE_WRITE]);
exit(1);
}
}
::execve(_path.c_str(), args, environ);
write_error(FAILED_EXECVE, errno, sigpipe[_PIPE_WRITE]);
exit(1);
} else {
_pid = pid;
close(sigpipe[_PIPE_WRITE]);
_stdin = inpipe[_PIPE_WRITE];
_stdout = outpipe[_PIPE_READ];
_stderr = errpipe[_PIPE_READ];
if (inpipe[_PIPE_READ] != -1) {
close(inpipe[_PIPE_READ]);
}
if (outpipe[_PIPE_WRITE] != -1) {
close(outpipe[_PIPE_WRITE]);
}
if (errpipe[_PIPE_WRITE] != -1) {
close(errpipe[_PIPE_WRITE]);
}
uint32_t code;
// The return code is unimportant, the read will return once the child process exits or execs
auto nr = read(sigpipe[_PIPE_READ], &code, sizeof(code));
if (nr == sizeof(code)) {
_fail_reason = static_cast<int>(code>>16);
_errno = static_cast<int>(code&0xFFFF);
} else {
_fail_reason = 0;
_errno = 0;
}
close(sigpipe[_PIPE_READ]);
return (-_errno);
}
}
// Send a signal to the process
int Cmd::Kill(int signum) {
if (_pid <= 0) {
return ESRCH;
}
return kill(_pid, signum);
}
// Wait for the process to exit. Returns 0, if the process is still running, 1 if the process has exited, -errno if the wait call failed.
int Cmd::Wait(bool wait) {
if (_pid <= 0) {
return 1;
}
int wstatus = 0;
errno = 0;
int ret;
do {
ret = waitpid(_pid, &wstatus, WNOHANG);
if (ret < 0 && errno != EINTR) {
return -errno;
}
} while (errno == EINTR);
if (ret != 0) {
if (ret == _pid) {
_pid = 0;
if (WIFEXITED(wstatus)) {
_exitcode = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
_signal = WTERMSIG(wstatus);
}
return 1;
} else if (errno == ECHILD) {
// waitpid will return ECHILD if the specified pid doesn't exist.
// If the caller has reaped the child via some other mechanism, then we'll get a ECHILD when we try to wait for it.
_pid = 0;
return 1;
}
} else if (wait) {
while(true) {
ret = waitpid(_pid, &wstatus, 0);
if (ret < 0) {
if (errno == EINTR) {
continue;
}
return -errno;
}
if (ret == _pid) {
_pid = 0;
if (WIFEXITED(wstatus)) {
_exitcode = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
_signal = WTERMSIG(wstatus);
}
return 1;
} else if (errno == ECHILD) {
// waitpid will return ECHILD if the specified pid doesn't exist.
// If the caller has reaped the child via some other mechanism, then we'll get a ECHILD when we try to wait for it.
_pid = 0;
return 1;
}
}
}
return 0;
}
static std::unordered_map<int, std::string> s_failed_call({
{Cmd::FAILED_FORK, "fork()"},
{Cmd::FAILED_PIPE, "pipe()"},
{Cmd::FAILED_PIPE2, "pipe2()"},
{Cmd::FAILED_OPEN, "open(/dev/null)"},
{Cmd::FAILED_DUP2, "dup2()"},
{Cmd::FAILED_PRCTL, "prctl()"},
{Cmd::FAILED_EXECVE, "execve()"},
});
std::string Cmd::FailMsg() {
std::string str = s_failed_call[_fail_reason];
str.append(" failed: ");
str.append(std::strerror(_errno));
return str;
}
int is_readable(int fd, int timeout) {
if (fd <= 0) {
return false;
}
struct pollfd fds;
fds.fd = fd;
fds.events = POLLIN;
fds.revents = 0;
auto ret = poll(&fds, 1, timeout);
if (ret < 0) {
if (errno != EINTR) {
return -1;
} else {
return 0;
}
} else if (ret == 0) {
return 0;
}
if ((fds.revents & POLLIN) != 0) {
return 1;
} if ((fds.revents & (POLLHUP&POLLRDHUP)) != 0) {
return -1;
} else {
return -1;
}
}
void* io_thread_entry(void* ptr) {
(*static_cast<std::function<void()>*>(ptr))();
return nullptr;
}
int Cmd::Run(std::string& output) {
auto ret = Start();
if (ret != 0) {
output = "Cmd::Start(): " + FailMsg();
return -1;
}
int fd = StdOutFd();
Gate io_gate1;
Gate io_gate2;
std::function<void()> fn = [&io_gate1, &io_gate2, &output, fd]() {
sigset_t set;
// Make sure this thread doesn't receive unwanted signals
sigfillset(&set);
pthread_sigmask(SIG_BLOCK, &set, NULL);
// Make sure the thread will get interrupted by SIGQUIT
sigemptyset(&set);
sigaddset(&set, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
std::array<char, 1024> buf;
ssize_t nr = 0;
do {
nr = read(fd, buf.data(), buf.size());
if (nr > 0) {
output.append(buf.data(), nr);
}
} while (nr > 0 || (nr < 0 && errno == EINTR && io_gate1.GetState() == Gate::CLOSED));
io_gate2.Open();
return nullptr;
};
pthread_t thread_id;
auto err = pthread_create(&thread_id, nullptr, io_thread_entry, &fn);
if (err != 0) {
throw std::system_error(err, std::system_category());
}
ret = Wait(true);
// Wait up to 100 milliseconds for io thread to finish
if (!io_gate2.Wait(Gate::OPEN, 100)) {
// io thread has not exited, kill it
io_gate1.Open();
pthread_kill(thread_id, SIGQUIT);
}
pthread_join(thread_id, nullptr);
if (ret < 0) {
auto err = errno;
output = "Failed to get process exit status: " + std::string(std::strerror(err));
}
if (Signal() > 0) {
output = "Process terminated with signal (" + std::to_string(Signal()) + ")";
return 1;
}
return ExitCode();
}