diff options
| -rw-r--r-- | runtime/base/safe_copy.cc | 43 | ||||
| -rw-r--r-- | runtime/base/safe_copy.h | 3 | ||||
| -rw-r--r-- | runtime/base/safe_copy_test.cc | 56 |
3 files changed, 91 insertions, 11 deletions
diff --git a/runtime/base/safe_copy.cc b/runtime/base/safe_copy.cc index b69a56ff06..06249acb44 100644 --- a/runtime/base/safe_copy.cc +++ b/runtime/base/safe_copy.cc @@ -18,9 +18,14 @@ #include <unistd.h> #include <sys/uio.h> +#include <sys/user.h> + +#include <algorithm> #include <android-base/macros.h> +#include "runtime/base/bit_utils.h" + namespace art { ssize_t SafeCopy(void *dst, const void *src, size_t len) { @@ -29,12 +34,40 @@ ssize_t SafeCopy(void *dst, const void *src, size_t len) { .iov_base = dst, .iov_len = len, }; - struct iovec src_iov = { - .iov_base = const_cast<void*>(src), - .iov_len = len, - }; - ssize_t rc = process_vm_readv(getpid(), &dst_iov, 1, &src_iov, 1, 0); + // Split up the remote read across page boundaries. + // From the manpage: + // A partial read/write may result if one of the remote_iov elements points to an invalid + // memory region in the remote process. + // + // Partial transfers apply at the granularity of iovec elements. These system calls won't + // perform a partial transfer that splits a single iovec element. + constexpr size_t kMaxIovecs = 64; + struct iovec src_iovs[kMaxIovecs]; + size_t iovecs_used = 0; + + const char* cur = static_cast<const char*>(src); + while (len > 0) { + if (iovecs_used == kMaxIovecs) { + errno = EINVAL; + return -1; + } + + src_iovs[iovecs_used].iov_base = const_cast<char*>(cur); + if (!IsAlignedParam(cur, PAGE_SIZE)) { + src_iovs[iovecs_used].iov_len = AlignUp(cur, PAGE_SIZE) - cur; + } else { + src_iovs[iovecs_used].iov_len = PAGE_SIZE; + } + + src_iovs[iovecs_used].iov_len = std::min(src_iovs[iovecs_used].iov_len, len); + + len -= src_iovs[iovecs_used].iov_len; + cur += src_iovs[iovecs_used].iov_len; + ++iovecs_used; + } + + ssize_t rc = process_vm_readv(getpid(), &dst_iov, 1, src_iovs, iovecs_used, 0); if (rc == -1) { return 0; } diff --git a/runtime/base/safe_copy.h b/runtime/base/safe_copy.h index 2eee2120fc..d0f497c0bd 100644 --- a/runtime/base/safe_copy.h +++ b/runtime/base/safe_copy.h @@ -22,7 +22,8 @@ namespace art { // Safely dereference a pointer. -// Returns -1 if safe copy isn't implemented on the platform, 0 if src is unreadable. +// Returns -1 if safe copy isn't implemented on the platform, or if the transfer is too large. +// Returns 0 if src is unreadable. ssize_t SafeCopy(void *dst, const void *src, size_t len); } // namespace art diff --git a/runtime/base/safe_copy_test.cc b/runtime/base/safe_copy_test.cc index d5b8cdb05d..987895e6b7 100644 --- a/runtime/base/safe_copy_test.cc +++ b/runtime/base/safe_copy_test.cc @@ -18,6 +18,8 @@ #include "common_runtime_test.h" +#include <errno.h> +#include <string.h> #include <sys/mman.h> #include <sys/user.h> @@ -26,31 +28,75 @@ namespace art { #if defined(__linux__) TEST(SafeCopyTest, smoke) { - // Map two pages, and mark the second one as PROT_NONE. - void* map = mmap(nullptr, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, + // Map four pages, mark the second one as PROT_NONE, unmap the last one. + void* map = mmap(nullptr, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); ASSERT_NE(MAP_FAILED, map); char* page1 = static_cast<char*>(map); + char* page2 = page1 + PAGE_SIZE; + char* page3 = page2 + PAGE_SIZE; + char* page4 = page3 + PAGE_SIZE; ASSERT_EQ(0, mprotect(page1 + PAGE_SIZE, PAGE_SIZE, PROT_NONE)); + ASSERT_EQ(0, munmap(page4, PAGE_SIZE)); page1[0] = 'a'; page1[PAGE_SIZE - 1] = 'z'; + page3[0] = 'b'; + page3[PAGE_SIZE - 1] = 'y'; + char buf[PAGE_SIZE]; // Completely valid read. memset(buf, 0xCC, sizeof(buf)); - EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE), SafeCopy(buf, page1, PAGE_SIZE)); + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE), SafeCopy(buf, page1, PAGE_SIZE)) << strerror(errno); EXPECT_EQ(0, memcmp(buf, page1, PAGE_SIZE)); - // Reading off of the end. + // Reading into a guard page. memset(buf, 0xCC, sizeof(buf)); EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE - 1), SafeCopy(buf, page1 + 1, PAGE_SIZE)); EXPECT_EQ(0, memcmp(buf, page1 + 1, PAGE_SIZE - 1)); + // Reading from a guard page into a real page. + memset(buf, 0xCC, sizeof(buf)); + EXPECT_EQ(0, SafeCopy(buf, page2 + PAGE_SIZE - 1, PAGE_SIZE)); + + // Reading off of the end of a mapping. + memset(buf, 0xCC, sizeof(buf)); + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE), SafeCopy(buf, page3, PAGE_SIZE * 2)); + EXPECT_EQ(0, memcmp(buf, page3, PAGE_SIZE)); + // Completely invalid. EXPECT_EQ(0, SafeCopy(buf, page1 + PAGE_SIZE, PAGE_SIZE)); - ASSERT_EQ(0, munmap(map, PAGE_SIZE * 2)); + + // Clean up. + ASSERT_EQ(0, munmap(map, PAGE_SIZE * 3)); +} + +TEST(SafeCopyTest, alignment) { + // Copy the middle of a mapping to the end of another one. + void* src_map = mmap(nullptr, PAGE_SIZE * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, src_map); + + // Add a guard page to make sure we don't write past the end of the mapping. + void* dst_map = mmap(nullptr, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, dst_map); + + char* src = static_cast<char*>(src_map); + char* dst = static_cast<char*>(dst_map); + ASSERT_EQ(0, mprotect(dst + 3 * PAGE_SIZE, PAGE_SIZE, PROT_NONE)); + + src[512] = 'a'; + src[PAGE_SIZE * 3 - 512 - 1] = 'z'; + + EXPECT_EQ(static_cast<ssize_t>(PAGE_SIZE * 3 - 1024), + SafeCopy(dst + 1024, src + 512, PAGE_SIZE * 3 - 1024)); + EXPECT_EQ(0, memcmp(dst + 1024, src + 512, PAGE_SIZE * 3 - 1024)); + + ASSERT_EQ(0, munmap(src_map, PAGE_SIZE * 3)); + ASSERT_EQ(0, munmap(dst_map, PAGE_SIZE * 4)); } #endif // defined(__linux__) |