| // 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 |