diff options
| -rw-r--r-- | runtime/Android.bp | 2 | ||||
| -rw-r--r-- | runtime/arch/x86/fault_handler_x86.cc | 57 | ||||
| -rw-r--r-- | runtime/base/safe_copy.cc | 48 | ||||
| -rw-r--r-- | runtime/base/safe_copy.h | 30 | ||||
| -rw-r--r-- | runtime/base/safe_copy_test.cc | 58 | ||||
| -rw-r--r-- | runtime/fault_handler.cc | 83 |
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; } |