summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/Android.bp2
-rw-r--r--runtime/arch/x86/fault_handler_x86.cc57
-rw-r--r--runtime/base/safe_copy.cc48
-rw-r--r--runtime/base/safe_copy.h30
-rw-r--r--runtime/base/safe_copy_test.cc58
-rw-r--r--runtime/fault_handler.cc83
6 files changed, 264 insertions, 14 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 6c3bc0450b..8972e91321 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -37,6 +37,7 @@ cc_defaults {
"base/hex_dump.cc",
"base/logging.cc",
"base/mutex.cc",
+ "base/safe_copy.cc",
"base/scoped_arena_allocator.cc",
"base/scoped_flock.cc",
"base/stringpiece.cc",
@@ -522,6 +523,7 @@ art_cc_test {
"base/hex_dump_test.cc",
"base/histogram_test.cc",
"base/mutex_test.cc",
+ "base/safe_copy_test.cc",
"base/scoped_flock_test.cc",
"base/time_utils_test.cc",
"base/timing_logger_test.cc",
diff --git a/runtime/arch/x86/fault_handler_x86.cc b/runtime/arch/x86/fault_handler_x86.cc
index f407ebf1d1..014cc15a40 100644
--- a/runtime/arch/x86/fault_handler_x86.cc
+++ b/runtime/arch/x86/fault_handler_x86.cc
@@ -25,6 +25,7 @@
#include "globals.h"
#include "base/logging.h"
#include "base/hex_dump.h"
+#include "base/safe_copy.h"
#include "thread.h"
#include "thread-inl.h"
@@ -78,6 +79,30 @@ extern "C" void art_quick_test_suspend();
// Get the size of an instruction in bytes.
// Return 0 if the instruction is not handled.
static uint32_t GetInstructionSize(const uint8_t* pc) {
+ // Don't segfault if pc points to garbage.
+ char buf[15]; // x86/x86-64 have a maximum instruction length of 15 bytes.
+ ssize_t bytes = SafeCopy(buf, pc, sizeof(buf));
+
+ if (bytes == 0) {
+ // Nothing was readable.
+ return 0;
+ }
+
+ if (bytes == -1) {
+ // SafeCopy not supported, assume that the entire range is readable.
+ bytes = 16;
+ } else {
+ pc = reinterpret_cast<uint8_t*>(buf);
+ }
+
+#define INCREMENT_PC() \
+ do { \
+ pc++; \
+ if (pc - startpc > bytes) { \
+ return 0; \
+ } \
+ } while (0)
+
#if defined(__x86_64)
const bool x86_64 = true;
#else
@@ -86,7 +111,8 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
const uint8_t* startpc = pc;
- uint8_t opcode = *pc++;
+ uint8_t opcode = *pc;
+ INCREMENT_PC();
uint8_t modrm;
bool has_modrm = false;
bool two_byte = false;
@@ -118,7 +144,8 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
// Group 4
case 0x67:
- opcode = *pc++;
+ opcode = *pc;
+ INCREMENT_PC();
prefix_present = true;
break;
}
@@ -128,13 +155,15 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
}
if (x86_64 && opcode >= 0x40 && opcode <= 0x4f) {
- opcode = *pc++;
+ opcode = *pc;
+ INCREMENT_PC();
}
if (opcode == 0x0f) {
// Two byte opcode
two_byte = true;
- opcode = *pc++;
+ opcode = *pc;
+ INCREMENT_PC();
}
bool unhandled_instruction = false;
@@ -147,7 +176,8 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
case 0xb7:
case 0xbe: // movsx
case 0xbf:
- modrm = *pc++;
+ modrm = *pc;
+ INCREMENT_PC();
has_modrm = true;
break;
default:
@@ -166,28 +196,32 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
case 0x3c:
case 0x3d:
case 0x85: // test.
- modrm = *pc++;
+ modrm = *pc;
+ INCREMENT_PC();
has_modrm = true;
break;
case 0x80: // group 1, byte immediate.
case 0x83:
case 0xc6:
- modrm = *pc++;
+ modrm = *pc;
+ INCREMENT_PC();
has_modrm = true;
immediate_size = 1;
break;
case 0x81: // group 1, word immediate.
case 0xc7: // mov
- modrm = *pc++;
+ modrm = *pc;
+ INCREMENT_PC();
has_modrm = true;
immediate_size = operand_size_prefix ? 2 : 4;
break;
case 0xf6:
case 0xf7:
- modrm = *pc++;
+ modrm = *pc;
+ INCREMENT_PC();
has_modrm = true;
switch ((modrm >> 3) & 7) { // Extract "reg/opcode" from "modr/m".
case 0: // test
@@ -222,7 +256,7 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
// Check for SIB.
if (mod != 3U /* 0b11 */ && (modrm & 7U /* 0b111 */) == 4) {
- ++pc; // SIB
+ INCREMENT_PC(); // SIB
}
switch (mod) {
@@ -238,6 +272,9 @@ static uint32_t GetInstructionSize(const uint8_t* pc) {
pc += displacement_size + immediate_size;
VLOG(signals) << "x86 instruction length calculated as " << (pc - startpc);
+ if (pc - startpc > bytes) {
+ return 0;
+ }
return pc - startpc;
}
diff --git a/runtime/base/safe_copy.cc b/runtime/base/safe_copy.cc
new file mode 100644
index 0000000000..b69a56ff06
--- /dev/null
+++ b/runtime/base/safe_copy.cc
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "safe_copy.h"
+
+#include <unistd.h>
+#include <sys/uio.h>
+
+#include <android-base/macros.h>
+
+namespace art {
+
+ssize_t SafeCopy(void *dst, const void *src, size_t len) {
+#if defined(__linux__)
+ struct iovec dst_iov = {
+ .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);
+ if (rc == -1) {
+ return 0;
+ }
+ return rc;
+#else
+ UNUSED(dst, src, len);
+ return -1;
+#endif
+}
+
+} // namespace art
diff --git a/runtime/base/safe_copy.h b/runtime/base/safe_copy.h
new file mode 100644
index 0000000000..2eee2120fc
--- /dev/null
+++ b/runtime/base/safe_copy.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_BASE_SAFE_COPY_H_
+#define ART_RUNTIME_BASE_SAFE_COPY_H_
+
+#include <sys/types.h>
+
+namespace art {
+
+// Safely dereference a pointer.
+// Returns -1 if safe copy isn't implemented on the platform, 0 if src is unreadable.
+ssize_t SafeCopy(void *dst, const void *src, size_t len);
+
+} // namespace art
+
+#endif // ART_RUNTIME_BASE_SAFE_COPY_H_
diff --git a/runtime/base/safe_copy_test.cc b/runtime/base/safe_copy_test.cc
new file mode 100644
index 0000000000..d5b8cdb05d
--- /dev/null
+++ b/runtime/base/safe_copy_test.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "safe_copy.h"
+
+#include "common_runtime_test.h"
+
+#include <sys/mman.h>
+#include <sys/user.h>
+
+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_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, map);
+ char* page1 = static_cast<char*>(map);
+ ASSERT_EQ(0, mprotect(page1 + PAGE_SIZE, PAGE_SIZE, PROT_NONE));
+
+ page1[0] = 'a';
+ page1[PAGE_SIZE - 1] = 'z';
+
+ 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(0, memcmp(buf, page1, PAGE_SIZE));
+
+ // Reading off of the end.
+ 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));
+
+ // Completely invalid.
+ EXPECT_EQ(0, SafeCopy(buf, page1 + PAGE_SIZE, PAGE_SIZE));
+ ASSERT_EQ(0, munmap(map, PAGE_SIZE * 2));
+}
+
+#endif // defined(__linux__)
+
+} // namespace art
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc
index 4220250c38..4ef3a67b96 100644
--- a/runtime/fault_handler.cc
+++ b/runtime/fault_handler.cc
@@ -21,8 +21,10 @@
#include <sys/ucontext.h>
#include "art_method-inl.h"
+#include "base/safe_copy.h"
#include "base/stl_util.h"
#include "mirror/class.h"
+#include "mirror/object_reference.h"
#include "oat_quick_method_header.h"
#include "sigchain.h"
#include "thread-inl.h"
@@ -42,6 +44,80 @@ static bool art_fault_handler(int sig, siginfo_t* info, void* context) {
return fault_manager.HandleFault(sig, info, context);
}
+#if defined(__linux__)
+
+// Change to verify the safe implementations against the original ones.
+constexpr bool kVerifySafeImpls = false;
+
+// Provide implementations of ArtMethod::GetDeclaringClass and VerifyClassClass that use SafeCopy
+// to safely dereference pointers which are potentially garbage.
+// Only available on Linux due to availability of SafeCopy.
+
+static mirror::Class* SafeGetDeclaringClass(ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ char* method_declaring_class =
+ reinterpret_cast<char*>(method) + ArtMethod::DeclaringClassOffset().SizeValue();
+
+ // ArtMethod::declaring_class_ is a GcRoot<mirror::Class>.
+ // Read it out into as a CompressedReference directly for simplicity's sake.
+ mirror::CompressedReference<mirror::Class> cls;
+ ssize_t rc = SafeCopy(&cls, method_declaring_class, sizeof(cls));
+ CHECK_NE(-1, rc);
+
+ if (kVerifySafeImpls) {
+ mirror::Class* actual_class = method->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
+ CHECK_EQ(actual_class, cls.AsMirrorPtr());
+ }
+
+ if (rc != sizeof(cls)) {
+ return nullptr;
+ }
+
+ return cls.AsMirrorPtr();
+}
+
+static mirror::Class* SafeGetClass(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+ char* obj_cls = reinterpret_cast<char*>(obj) + mirror::Object::ClassOffset().SizeValue();
+
+ mirror::CompressedReference<mirror::Class> cls;
+ ssize_t rc = SafeCopy(&cls, obj_cls, sizeof(cls));
+ CHECK_NE(-1, rc);
+
+ if (kVerifySafeImpls) {
+ mirror::Class* actual_class = obj->GetClass<kVerifyNone>();
+ CHECK_EQ(actual_class, cls.AsMirrorPtr());
+ }
+
+ if (rc != sizeof(cls)) {
+ return nullptr;
+ }
+
+ return cls.AsMirrorPtr();
+}
+
+static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Class* c_c = SafeGetClass(cls);
+ bool result = c_c != nullptr && c_c == SafeGetClass(c_c);
+
+ if (kVerifySafeImpls) {
+ CHECK_EQ(VerifyClassClass(cls), result);
+ }
+
+ return result;
+}
+
+#else
+
+static mirror::Class* SafeGetDeclaringClass(ArtMethod* method_obj) {
+ return method_obj->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
+}
+
+static bool SafeVerifyClassClass(ArtClass* cls) {
+ return VerifyClassClass(cls);
+}
+#endif
+
+
FaultManager::FaultManager() : initialized_(false) {
sigaction(SIGSEGV, nullptr, &oldaction_);
}
@@ -191,20 +267,19 @@ bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context, bool che
// Verify that the potential method is indeed a method.
// TODO: check the GC maps to make sure it's an object.
// Check that the class pointer inside the object is not null and is aligned.
- // TODO: Method might be not a heap address, and GetClass could fault.
// No read barrier because method_obj may not be a real object.
- mirror::Class* cls = method_obj->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
+ mirror::Class* cls = SafeGetDeclaringClass(method_obj);
if (cls == nullptr) {
VLOG(signals) << "not a class";
return false;
}
+
if (!IsAligned<kObjectAlignment>(cls)) {
VLOG(signals) << "not aligned";
return false;
}
-
- if (!VerifyClassClass(cls)) {
+ if (!SafeVerifyClassClass(cls)) {
VLOG(signals) << "not a class class";
return false;
}