base: make SafeCopy work on older Linux kernels.
process_vm_readv is documented to not split transfer across a single
iovec element, but this appears to not be the case for the newer
kernels we have on our workstations.
Split up transfers across page boundaries, to avoid this. This has the
side effect of limiting the maximum size of a transfer, but 64 pages
ought to be enough for anybody.
Test: safe_copy_test on 3.13.0-101-generic (failing before)
Test: safe_copy_test on 4.4.0-66-generic (still passing)
Change-Id: I6a6dbf0cd2aeaa7eab39f6d41285d46ebd760e30
diff --git a/runtime/base/safe_copy.cc b/runtime/base/safe_copy.cc
index b69a56f..06249ac 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 @@
.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 2eee212..d0f497c 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 d5b8cdb..987895e 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 @@
#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__)