Skip to content

Commit

Permalink
Use pread/write of /proc/$sandbox_pid/mem when process_vm_readv and p…
Browse files Browse the repository at this point in the history
…rocess_vm_writev are unavailable.

PiperOrigin-RevId: 624945353
Change-Id: I79cf07ad41a9b842740754dad101776cfa4af745
  • Loading branch information
Michael Norris authored and copybara-github committed Apr 15, 2024
1 parent 0302244 commit af2d5c4
Show file tree
Hide file tree
Showing 9 changed files with 434 additions and 63 deletions.
3 changes: 3 additions & 0 deletions sandboxed_api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ cc_library(
"//sandboxed_api/sandbox2",
"//sandboxed_api/sandbox2:client",
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2:util",
"//sandboxed_api/util:file_base",
"//sandboxed_api/util:fileops",
"//sandboxed_api/util:runfiles",
Expand Down Expand Up @@ -167,6 +168,7 @@ cc_library(
":proto_arg_cc_proto",
":var_type",
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2:util",
"//sandboxed_api/util:status",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/log",
Expand All @@ -176,6 +178,7 @@ cc_library(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/types:span",
"@com_google_absl//absl/utility",
],
)
Expand Down
2 changes: 2 additions & 0 deletions sandboxed_api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,15 @@ add_library(sapi_vars ${SAPI_LIB_TYPE}
add_library(sapi::vars ALIAS sapi_vars)
target_link_libraries(sapi_vars
PRIVATE absl::core_headers
absl::span
absl::status
absl::statusor
absl::str_format
absl::strings
absl::synchronization
absl::utility
sandbox2::comms
sandbox2::util
sapi::base
sapi::call
sapi::lenval_core
Expand Down
22 changes: 7 additions & 15 deletions sandboxed_api/sandbox.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <sys/uio.h>
#include <syscall.h>

#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <initializer_list>
Expand Down Expand Up @@ -46,6 +47,7 @@
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/result.h"
#include "sandboxed_api/sandbox2/sandbox2.h"
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/util/path.h"
#include "sandboxed_api/util/runfiles.h"
#include "sandboxed_api/util/status_macros.h"
Expand Down Expand Up @@ -450,21 +452,11 @@ absl::StatusOr<std::string> Sandbox::GetCString(const v::RemotePtr& str,
absl::StrCat("Target string too large: ", len, " > ", max_length));
}
std::string buffer(len, '\0');
struct iovec local = {
.iov_base = &buffer[0],
.iov_len = len,
};
struct iovec remote = {
.iov_base = str.GetValue(),
.iov_len = len,
};

ssize_t ret = process_vm_readv(pid_, &local, 1, &remote, 1, 0);
if (ret == -1) {
PLOG(WARNING) << "reading c-string failed: process_vm_readv(pid: " << pid_
<< " raddr: " << str.GetValue() << " size: " << len << ")";
return absl::UnavailableError("process_vm_readv failed");
}
SAPI_ASSIGN_OR_RETURN(
size_t ret,
sandbox2::util::ReadBytesFromPidInto(
pid_, reinterpret_cast<uintptr_t>(str.GetValue()),
absl::MakeSpan(reinterpret_cast<char*>(buffer.data()), len)));
if (ret != len) {
LOG(WARNING) << "partial read when reading c-string: process_vm_readv(pid: "
<< pid_ << " raddr: " << str.GetValue() << " size: " << len
Expand Down
3 changes: 3 additions & 0 deletions sandboxed_api/sandbox2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,7 @@ cc_library(
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:span",
],
)

Expand Down Expand Up @@ -1034,8 +1035,10 @@ cc_test(
"//sandboxed_api:testing",
"//sandboxed_api/util:status_matchers",
"@com_google_absl//absl/cleanup",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
Expand Down
5 changes: 4 additions & 1 deletion sandboxed_api/sandbox2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,8 @@ target_link_libraries(sandbox2_util
sapi::base
sapi::raw_logging
sapi::status
PUBLIC absl::status
PUBLIC absl::span
absl::status
absl::statusor
)
target_compile_options(sandbox2_util PRIVATE
Expand Down Expand Up @@ -1116,7 +1117,9 @@ if(BUILD_TESTING AND SAPI_BUILD_TESTING)
sandbox2::util
absl::statusor
absl::strings
absl::check
absl::cleanup
absl::span
sapi::status_matchers
sapi::testing
sapi::test_main
Expand Down
171 changes: 156 additions & 15 deletions sandboxed_api/sandbox2/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "sandboxed_api/sandbox2/util.h"

#include <fcntl.h>
#include <linux/limits.h>
#include <sched.h>
#include <spawn.h>
Expand Down Expand Up @@ -50,6 +51,7 @@
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/util/file_helpers.h"
#include "sandboxed_api/util/fileops.h"
Expand Down Expand Up @@ -410,39 +412,178 @@ std::string GetPtraceEventName(int event) {
}
}

absl::StatusOr<std::vector<uint8_t>> ReadBytesFromPid(pid_t pid, uintptr_t ptr,
uint64_t size) {
namespace {

// Transfer memory via process_vm_readv/process_vm_writev.
absl::StatusOr<size_t> ProcessVmTransfer(bool is_read, pid_t pid, uintptr_t ptr,
absl::Span<char> data) {
static const uintptr_t page_size = getpagesize();
static const uintptr_t page_mask = page_size - 1;

// Input sanity checks.
if (size == 0) {
return std::vector<uint8_t>();
if (data.empty()) {
return 0;
}

// Allocate enough bytes to hold the entire size.
std::vector<uint8_t> bytes(size, 0);
iovec local_iov[] = {{bytes.data(), bytes.size()}};
iovec local_iov[] = {{data.data(), data.size()}};
// Stores all the necessary iovecs to move memory.
std::vector<iovec> remote_iov;
// Each iovec should be contained to a single page.
size_t consumed = 0;
while (consumed < size) {
while (consumed < data.size()) {
// Read till the end of the page, at most the remaining number of bytes.
size_t chunk_size =
std::min(size - consumed, page_size - ((ptr + consumed) & page_mask));
size_t chunk_size = std::min(data.size() - consumed,
page_size - ((ptr + consumed) & page_mask));
remote_iov.push_back({reinterpret_cast<void*>(ptr + consumed), chunk_size});
consumed += chunk_size;
}

ssize_t result = process_vm_readv(pid, local_iov, ABSL_ARRAYSIZE(local_iov),
remote_iov.data(), remote_iov.size(), 0);
ssize_t result =
is_read ? process_vm_readv(pid, local_iov, ABSL_ARRAYSIZE(local_iov),
remote_iov.data(), remote_iov.size(), 0)
: process_vm_writev(pid, local_iov, ABSL_ARRAYSIZE(local_iov),
remote_iov.data(), remote_iov.size(), 0);
if (result < 0) {
return absl::ErrnoToStatus(
errno,
absl::StrFormat("process_vm_readv() failed for PID: %d at address: %#x",
pid, ptr));
errno, absl::StrFormat("transfer() failed for PID: %d at address: %#x",
pid, ptr));
}
return result;
}

// Open /proc/pid/mem file descriptor.
absl::StatusOr<file_util::fileops::FDCloser> OpenProcMem(pid_t pid,
bool is_read) {
auto path = absl::StrFormat("/proc/%d/mem", pid);
auto closer = file_util::fileops::FDCloser(
open(path.c_str(), is_read ? O_RDONLY : O_WRONLY));
if (closer.get() == -1) {
return absl::ErrnoToStatus(
errno, absl::StrFormat("open() failed for PID: %d", pid));
}
return closer;
}

absl::StatusOr<size_t> ProcMemTransfer(bool is_read, pid_t pid, uintptr_t ptr,
absl::Span<char> data) {
if (data.empty()) {
return 0;
}

SAPI_ASSIGN_OR_RETURN(file_util::fileops::FDCloser fd_closer,
OpenProcMem(pid, is_read));
size_t total_bytes_transferred = 0;
while (!data.empty()) {
ssize_t bytes_transfered =
is_read ? bytes_transfered =
pread(fd_closer.get(), data.data(), data.size(), ptr)
: bytes_transfered =
pwrite(fd_closer.get(), data.data(), data.size(), ptr);
if (bytes_transfered == 0) {
if (total_bytes_transferred == 0) {
return absl::NotFoundError(absl::StrFormat(
"Transfer was unsuccessful for PID: %d at address: %#x", pid, ptr));
}
break;
} else if (bytes_transfered < 0) {
if (total_bytes_transferred > 0) {
// Return number of bytes transferred until this error or end.
break;
}
// pread/write of /proc/<pid>mem returns EIO when ptr is unmapped.
if (errno == EIO) {
// Emulate returned error code from process_vm_readv.
errno = EFAULT;
}
return absl::ErrnoToStatus(
errno,
absl::StrFormat("transfer() failed for PID: %d at address: %#x", pid,
ptr));
}
ptr += bytes_transfered;
data = data.subspan(bytes_transfered, data.size() - bytes_transfered);
total_bytes_transferred += bytes_transfered;
}
return total_bytes_transferred;
}

bool CheckIfProcessVmTransferWorks() {
// Fall-back to pread("/proc/$pid/mem") if process_vm_readv is unavailable.
static bool process_vm_transfer_works = []() {
constexpr char kMagic = 42;
char src = kMagic;
char dst = 0;
absl::StatusOr<size_t> read = internal::ReadBytesFromPidWithReadv(
getpid(), reinterpret_cast<uintptr_t>(&src), absl::MakeSpan(&dst, 1));
if (!read.ok() || *read != 1 || dst != kMagic) {
SAPI_RAW_LOG(WARNING,
"This system does not seem to support the process_vm_readv()"
" or process_vm_writev syscall. Falling back to transfers"
" via /proc/pid/mem.");
return false;
}
return true;
}();
return process_vm_transfer_works;
}

} // namespace

namespace internal {

absl::StatusOr<size_t> ReadBytesFromPidWithReadv(pid_t pid, uintptr_t ptr,
absl::Span<char> data) {
return ProcessVmTransfer(true, pid, ptr, data);
}

absl::StatusOr<size_t> WriteBytesToPidWithWritev(pid_t pid, uintptr_t ptr,
absl::Span<const char> data) {
return ProcessVmTransfer(
false, pid, ptr,
absl::MakeSpan(const_cast<char*>(data.data()), data.size()));
}

absl::StatusOr<size_t> ReadBytesFromPidWithProcMem(pid_t pid, uintptr_t ptr,
absl::Span<char> data) {
return ProcMemTransfer(true, pid, ptr, data);
}

absl::StatusOr<size_t> WriteBytesToPidWithProcMem(pid_t pid, uintptr_t ptr,
absl::Span<const char> data) {
return ProcMemTransfer(
false, pid, ptr,
absl::MakeSpan(const_cast<char*>(data.data()), data.size()));
}

} // namespace internal

absl::StatusOr<size_t> ReadBytesFromPidInto(pid_t pid, uintptr_t ptr,
absl::Span<char> data) {
if (CheckIfProcessVmTransferWorks()) {
return internal::ReadBytesFromPidWithReadv(pid, ptr, data);
} else {
return internal::ReadBytesFromPidWithProcMem(pid, ptr, data);
}
}

absl::StatusOr<size_t> WriteBytesToPidFrom(pid_t pid, uintptr_t ptr,
absl::Span<const char> data) {
if (CheckIfProcessVmTransferWorks()) {
return internal::WriteBytesToPidWithWritev(pid, ptr, data);
} else {
return internal::WriteBytesToPidWithProcMem(pid, ptr, data);
}
}

absl::StatusOr<std::vector<uint8_t>> ReadBytesFromPid(pid_t pid, uintptr_t ptr,
size_t size) {
// Allocate enough bytes to hold the entire size.
std::vector<uint8_t> bytes(size, 0);
SAPI_ASSIGN_OR_RETURN(
size_t result,
ReadBytesFromPidInto(
pid, ptr,
absl::MakeSpan(reinterpret_cast<char*>(bytes.data()), size)));
// Ensure only successfully read bytes are returned.
bytes.resize(result);
return bytes;
Expand Down
33 changes: 32 additions & 1 deletion sandboxed_api/sandbox2/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@

#include <sys/types.h>

#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>

#include "absl/base/attributes.h"
#include "absl/base/macros.h"
#include "absl/status/statusor.h"
#include "absl/types/span.h"

namespace sandbox2::util {

Expand Down Expand Up @@ -107,9 +109,38 @@ std::string GetRlimitName(int resource);
// Returns ptrace event name
std::string GetPtraceEventName(int event);

namespace internal {
// Reads `data`'s length of bytes from `ptr` in `pid`, returns number of bytes
// read or an error.
absl::StatusOr<size_t> ReadBytesFromPidWithReadv(pid_t pid, uintptr_t ptr,
absl::Span<char> data);

// Writes `data` to `ptr` in `pid`, returns number of bytes written or an error.
absl::StatusOr<size_t> WriteBytesToPidWithWritev(pid_t pid, uintptr_t ptr,
absl::Span<const char> data);

// Reads `data`'s length of bytes from `ptr` in `pid`, returns number of bytes
// read or an error.
absl::StatusOr<size_t> ReadBytesFromPidWithProcMem(pid_t pid, uintptr_t ptr,
absl::Span<char> data);

// Writes `data` to `ptr` in `pid`, returns number of bytes written or an error.
absl::StatusOr<size_t> WriteBytesToPidWithProcMem(pid_t pid, uintptr_t ptr,
absl::Span<const char> data);
}; // namespace internal

// Reads `data`'s length of bytes from `ptr` in `pid`, returns number of bytes
// read or an error.
absl::StatusOr<size_t> ReadBytesFromPidInto(pid_t pid, uintptr_t ptr,
absl::Span<char> data);

// Writes `data` to `ptr` in `pid`, returns number of bytes written or an error.
absl::StatusOr<size_t> WriteBytesToPidFrom(pid_t pid, uintptr_t remote_ptr,
absl::Span<const char> data);

// Reads `size` bytes from the given `ptr` address, or returns an error.
absl::StatusOr<std::vector<uint8_t>> ReadBytesFromPid(pid_t pid, uintptr_t ptr,
uint64_t size);
size_t size);

// Reads a path string (NUL-terminated, shorter than PATH_MAX) from another
// process memory
Expand Down
Loading

0 comments on commit af2d5c4

Please sign in to comment.