blob: d1506200b2a0ca1d5f28fd8f59bc3e7229abc32e [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "authpolicy/platform_helper.h"
#include <fcntl.h>
#include <linux/capability.h>
#include <poll.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sysexits.h>
#include <cstdint>
#include <sys/ioctl.h>
#include <algorithm>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
namespace authpolicy {
namespace {
// Size limit on the total number of bytes to read from a pipe.
const size_t kMaxReadSize = 16 * 1024 * 1024; // 16 MB
// The size of the buffer used to read from a pipe.
const size_t kBufferSize = PIPE_BUF; // ~4 Kb on my system
// Timeout used for poll()ing pipes.
const int kPollTimeoutMilliseconds = 30000;
// Creates a non-blocking local pipe and returns the read and the write end.
bool CreatePipe(base::ScopedFD* pipe_read_end, base::ScopedFD* pipe_write_end) {
int pipe_fd[2];
if (!base::CreateLocalNonBlockingPipe(pipe_fd)) {
LOG(ERROR) << "Failed to create pipe";
return false;
}
pipe_read_end->reset(pipe_fd[0]);
pipe_write_end->reset(pipe_fd[1]);
return true;
}
// Reads up to |kBufferSize| bytes from the file descriptor |src_fd| and appends
// them to |dst_str|. Sets |done| to true iff the whole file was read
// successfully. Might block if |src_fd| is a blocking pipe. Returns false on
// error.
bool ReadPipe(int src_fd, std::string* dst_str, bool* done) {
*done = false;
char buffer[kBufferSize];
const ssize_t bytes_read = HANDLE_EINTR(read(src_fd, buffer, kBufferSize));
if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
PLOG(ERROR) << "read() from fd " << src_fd << " failed";
return false;
}
if (bytes_read == 0)
*done = true;
else if (bytes_read > 0)
dst_str->append(buffer, bytes_read);
return true;
}
// Splices (copies) as much as possible data from the file descriptor |src_fd|
// to |dst_fd|. Sets |done| to true iff the whole |src_fd| file was spliced
// successfully. Might block if |src_fd| or |dst_fd| are blocking pipes. Returns
// false on error.
bool SplicePipe(int dst_fd, int src_fd, bool* done) {
*done = false;
const int flags = SPLICE_F_NONBLOCK | SPLICE_F_MORE | SPLICE_F_MOVE;
const ssize_t bytes_written =
HANDLE_EINTR(splice(src_fd, nullptr, dst_fd, nullptr, INT_MAX, flags));
if (bytes_written < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
PLOG(ERROR) << "splice() " << src_fd << " failed";
return false;
}
if (bytes_written == 0)
*done = true;
return true;
}
// Writes as much as possible from |src_str|, beginning at position |src_pos|,
// to the file descriptor |dst_fd|. Sets |done| to true iff the whole string
// was written successfully. Handles blocking |dst_fd|. Returns false on error.
// On success, increases |src_pos| by the number of bytes written.
bool WritePipe(int dst_fd,
const std::string& src_str,
size_t* src_pos,
bool* done) {
DCHECK_LE(*src_pos, src_str.size());
// Writing 0 bytes might not be well defined, so early out in this case.
if (*src_pos == src_str.size()) {
*done = true;
return true;
}
*done = false;
const ssize_t bytes_written = HANDLE_EINTR(
write(dst_fd, src_str.data() + *src_pos, src_str.size() - *src_pos));
if (bytes_written < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
PLOG(ERROR) << "write() to " << dst_fd << " failed";
return false;
}
if (bytes_written > 0) {
*src_pos += bytes_written;
DCHECK_LE(*src_pos, src_str.size());
}
if (*src_pos == src_str.size())
*done = true;
return true;
}
} // namespace
bool ReadPipeToString(int fd, std::string* out) {
char buffer[kBufferSize];
size_t total_read = 0;
while (total_read < kMaxReadSize) {
const ssize_t bytes_read = HANDLE_EINTR(
read(fd, buffer, std::min(kBufferSize, kMaxReadSize - total_read)));
if (bytes_read < 0)
return false;
if (bytes_read == 0)
return true;
total_read += bytes_read;
out->append(buffer, bytes_read);
}
// Size limit hit. Do one more read to check if the file size is exactly
// kMaxReadSize bytes.
return HANDLE_EINTR(read(fd, buffer, 1)) == 0;
}
base::ScopedFD ReadFileToPipe(const base::FilePath& path) {
base::ScopedFD pipe_read_end, pipe_write_end;
if (!authpolicy::CreatePipe(&pipe_read_end, &pipe_write_end))
return base::ScopedFD();
bool done = false;
base::ScopedFD file_fd(open(path.value().c_str(), O_RDONLY | O_NONBLOCK));
if (!file_fd.is_valid()) {
PLOG(ERROR) << "Failed to open file '" << path.value() << "'";
return base::ScopedFD();
}
// Splice twice. The first splice reads the whole file, but doesn't set
// |done|. The second splice sets |done| to true.
if (!SplicePipe(pipe_write_end.get(), file_fd.get(), &done) ||
!SplicePipe(pipe_write_end.get(), file_fd.get(), &done)) {
return base::ScopedFD();
}
if (!done) {
LOG(ERROR) << "Failed to splice the whole pipe in one go";
return base::ScopedFD();
}
return pipe_read_end;
}
bool PerformPipeIo(int stdin_fd,
int stdout_fd,
int stderr_fd,
int input_fd,
const std::string& input_str,
std::string* stdout,
std::string* stderr) {
// Make sure pipes get closed when exiting the scope.
base::ScopedFD stdin_scoped_fd(stdin_fd);
base::ScopedFD stdout_scoped_fd(stdout_fd);
base::ScopedFD stderr_scoped_fd(stderr_fd);
size_t input_str_pos = 0;
bool splicing_input_fd = (input_fd != -1);
while (stdin_scoped_fd.is_valid() || stdout_scoped_fd.is_valid() ||
stderr_scoped_fd.is_valid()) {
// Note that closed files (*_scoped_fd.get() == -1) are ignored.
const int kIndexStdin = 0, kIndexStdout = 1, kIndexStderr = 2;
const char* kPipeNames[] = {"stdin", "stdout", "stderr"};
const int kPollCount = 3;
struct pollfd poll_fds[kPollCount];
poll_fds[kIndexStdin] = {stdin_scoped_fd.get(), POLLOUT, 0};
poll_fds[kIndexStdout] = {stdout_scoped_fd.get(), POLLIN, 0};
poll_fds[kIndexStderr] = {stderr_scoped_fd.get(), POLLIN, 0};
const int poll_result =
HANDLE_EINTR(poll(poll_fds, kPollCount, kPollTimeoutMilliseconds));
if (poll_result < 0) {
PLOG(ERROR) << "poll() failed";
return false;
}
// Treat POLLNVAL as an error for all pipes.
for (int n : {kIndexStdin, kIndexStdout, kIndexStderr}) {
if ((poll_fds[n].revents & POLLNVAL) == 0)
continue;
LOG(ERROR) << "POLLNVAL for " << kPipeNames[n];
return false;
}
// Special case: POLLERR can legitimately happen on the child process'
// stdin if it already exited. Do not treat that as an error unless
// there was data the parent process wanted to send to the child
// process.
for (int n : {kIndexStdin, kIndexStdout, kIndexStderr}) {
if ((poll_fds[n].revents & POLLERR) == 0)
continue;
if (n == kIndexStdin) {
if (!splicing_input_fd && input_str.size() == input_str_pos) {
VLOG(1) << "Ignoring POLLERR for stdin.";
stdin_scoped_fd.reset();
continue;
}
LOG(ERROR) << "Child process stdin closed due to POLLERR when "
<< "there was still data to write.";
}
LOG(ERROR) << "POLLERR for " << kPipeNames[n];
return false;
}
// Should only happen on timeout. Log a warning here, so we get at least a
// log if the process is stale.
if (poll_result == 0)
LOG(WARNING) << "poll() timed out. Process might be stale.";
// Read stdout to the stdout string.
if (stdout_scoped_fd.is_valid() &&
(poll_fds[kIndexStdout].revents & (POLLIN | POLLHUP))) {
bool done = false;
if (!ReadPipe(stdout_scoped_fd.get(), stdout, &done))
return false;
else if (done)
stdout_scoped_fd.reset();
}
// Read stderr to the stderr string.
if (stderr_scoped_fd.is_valid() &&
(poll_fds[kIndexStderr].revents & (POLLIN | POLLHUP))) {
bool done = false;
if (!ReadPipe(stderr_scoped_fd.get(), stderr, &done))
return false;
else if (done)
stderr_scoped_fd.reset();
}
if (stdin_scoped_fd.is_valid() && poll_fds[kIndexStdin].revents & POLLOUT) {
bool done = false;
if (splicing_input_fd) {
// Splice input_fd to stdin_scoped_fd.
if (!SplicePipe(stdin_scoped_fd.get(), input_fd, &done))
return false;
else if (done)
splicing_input_fd = false;
} else {
// Write input_str to stdin_scoped_fd.
if (!WritePipe(stdin_scoped_fd.get(), input_str, &input_str_pos, &done))
return false;
else if (done)
stdin_scoped_fd.reset();
}
}
// Check size limits.
if (stdout->size() > kMaxReadSize || stderr->size() > kMaxReadSize) {
LOG(ERROR) << "Hit size limit";
return false;
}
}
return true;
}
base::ScopedFD DuplicatePipe(int src_fd) {
base::ScopedFD pipe_read_end, pipe_write_end;
if (!authpolicy::CreatePipe(&pipe_read_end, &pipe_write_end))
return base::ScopedFD();
if (HANDLE_EINTR(
tee(src_fd, pipe_write_end.get(), INT_MAX, SPLICE_F_NONBLOCK)) <= 0) {
PLOG(ERROR) << "Failed to duplicate pipe";
return base::ScopedFD();
}
return pipe_read_end;
}
base::ScopedFD WriteStringToPipe(const std::string& str) {
base::ScopedFD pipe_read_end, pipe_write_end;
if (!authpolicy::CreatePipe(&pipe_read_end, &pipe_write_end))
return base::ScopedFD();
if (!base::WriteFileDescriptor(pipe_write_end.get(), str.data(),
str.size())) {
LOG(ERROR) << "Failed to write string to pipe";
return base::ScopedFD();
}
return pipe_read_end;
}
base::ScopedFD WriteStringAndPipeToPipe(const std::string& str, int fd) {
base::ScopedFD pipe_read_end, pipe_write_end;
if (!authpolicy::CreatePipe(&pipe_read_end, &pipe_write_end))
return base::ScopedFD();
if (!base::WriteFileDescriptor(pipe_write_end.get(), str.data(),
str.size())) {
LOG(ERROR) << "Failed to write string to pipe";
return base::ScopedFD();
}
if (HANDLE_EINTR(tee(fd, pipe_write_end.get(), INT_MAX, SPLICE_F_NONBLOCK)) <=
0) {
PLOG(ERROR) << "Failed to duplicate pipe";
return base::ScopedFD();
}
return pipe_read_end;
}
uid_t GetEffectiveUserId() {
return geteuid();
}
bool SetSavedUserAndDropCaps(uid_t saved_uid) {
// Only set the saved UID, keep the other ones.
if (setresuid(-1, -1, saved_uid)) {
PLOG(ERROR) << "setresuid failed";
return false;
}
// Drop capabilities from bounding set.
if (prctl(PR_CAPBSET_DROP, CAP_SETUID) ||
prctl(PR_CAPBSET_DROP, CAP_SETPCAP)) {
PLOG(ERROR) << "Failed to drop caps from bounding set";
return false;
}
// Clear capabilities.
cap_t caps = cap_get_proc();
if (!caps || cap_clear_flag(caps, CAP_INHERITABLE) ||
cap_clear_flag(caps, CAP_EFFECTIVE) ||
cap_clear_flag(caps, CAP_PERMITTED) || cap_set_proc(caps)) {
PLOG(ERROR) << "Clearing caps failed";
return false;
}
return true;
}
ScopedSwitchToSavedUid::ScopedSwitchToSavedUid() {
uid_t real_uid, effective_uid;
CHECK_EQ(0, getresuid(&real_uid, &effective_uid, &saved_uid_));
CHECK(real_uid == effective_uid);
real_and_effective_uid_ = real_uid;
CHECK_EQ(0, setresuid(saved_uid_, saved_uid_, real_and_effective_uid_));
}
ScopedSwitchToSavedUid::~ScopedSwitchToSavedUid() {
CHECK_EQ(0, setresuid(real_and_effective_uid_, real_and_effective_uid_,
saved_uid_));
}
} // namespace authpolicy