Add basic support for object pointer poisoning
ObjPtr is a pointer that checks for heap corruption and is meant
to replace mirror::Object* in places where a mirror::Object* is a
local variable. Whenever there is a possible suspend point, the
current thread's object pointers are all invalidated. This is done
by storing a cookie in the object pointer associated with what thread
created it.
Added test case in object_test.
Example failure:
object_test F 25379 25379 object_pointer.h:70] Check failed:
IsValid() Invalid cookie, expected 0 but got 2
Bug: 31113334
Test: test-art-host-gtest-object_test
Change-Id: I9fa80ccaf2f0448621942935af702a243a3e1ee6
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 845e39a..2dbb1fd 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2378,6 +2378,7 @@
DCHECK_NE(*descriptor, '\0') << "descriptor is empty string";
DCHECK(self != nullptr);
self->AssertNoPendingException();
+ self->PoisonObjectPointers();
if (descriptor[1] == '\0') {
// only the descriptors of primitive types should be 1 character long, also avoid class lookup
// for primitive classes that aren't backed by dex files.
diff --git a/runtime/mirror/obj_ptr.h b/runtime/mirror/obj_ptr.h
new file mode 100644
index 0000000..10378e8
--- /dev/null
+++ b/runtime/mirror/obj_ptr.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 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_MIRROR_OBJ_PTR_H_
+#define ART_RUNTIME_MIRROR_OBJ_PTR_H_
+
+#include "base/mutex.h" // For Locks::mutator_lock_.
+#include "globals.h"
+#include "mirror/object_reference.h"
+#include "utils.h"
+
+namespace art {
+namespace mirror {
+
+class Object;
+
+// Value type representing a pointer to a mirror::Object of type MirrorType
+// Pass kPoison as a template boolean for testing in non-debug builds.
+// Note that the functions are not 100% thread safe and may have spurious positive check passes in
+// these cases.
+template<class MirrorType, bool kPoison = kIsDebugBuild>
+class ObjPtr {
+ static constexpr size_t kCookieShift =
+ sizeof(mirror::HeapReference<mirror::Object>) * kBitsPerByte - kObjectAlignmentShift;
+ static constexpr size_t kCookieBits = sizeof(uintptr_t) * kBitsPerByte - kCookieShift;
+ static constexpr uintptr_t kCookieMask = (static_cast<uintptr_t>(1u) << kCookieBits) - 1;
+
+ static_assert(kCookieBits >= kObjectAlignmentShift,
+ "must have a least kObjectAlignmentShift bits");
+
+ public:
+ ALWAYS_INLINE ObjPtr() REQUIRES_SHARED(Locks::mutator_lock_) : reference_(0u) {}
+
+ ALWAYS_INLINE explicit ObjPtr(MirrorType* ptr) REQUIRES_SHARED(Locks::mutator_lock_)
+ : reference_(Encode(ptr)) {}
+
+ ALWAYS_INLINE explicit ObjPtr(const ObjPtr& other) REQUIRES_SHARED(Locks::mutator_lock_)
+ = default;
+
+ ALWAYS_INLINE ObjPtr& operator=(const ObjPtr& other) {
+ reference_ = other.reference_;
+ return *this;
+ }
+
+ ALWAYS_INLINE ObjPtr& operator=(MirrorType* ptr) REQUIRES_SHARED(Locks::mutator_lock_) {
+ Assign(ptr);
+ return *this;
+ }
+
+ ALWAYS_INLINE void Assign(MirrorType* ptr) REQUIRES_SHARED(Locks::mutator_lock_) {
+ reference_ = Encode(ptr);
+ }
+
+ ALWAYS_INLINE MirrorType* operator->() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return Get();
+ }
+
+ ALWAYS_INLINE MirrorType* Get() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return Decode();
+ }
+
+ ALWAYS_INLINE bool IsNull() const {
+ return reference_ == 0;
+ }
+
+ ALWAYS_INLINE bool IsValid() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!kPoison || IsNull()) {
+ return true;
+ }
+ return GetCookie() == TrimCookie(Thread::Current()->GetPoisonObjectCookie());
+ }
+
+ ALWAYS_INLINE void AssertValid() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (kPoison) {
+ CHECK(IsValid()) << "Stale object pointer, expected cookie "
+ << TrimCookie(Thread::Current()->GetPoisonObjectCookie()) << " but got " << GetCookie();
+ }
+ }
+
+ ALWAYS_INLINE bool operator==(const ObjPtr& ptr) const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return Decode() == ptr.Decode();
+ }
+
+ ALWAYS_INLINE bool operator==(const MirrorType* ptr) const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return Decode() == ptr;
+ }
+
+ ALWAYS_INLINE bool operator==(std::nullptr_t) const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return IsNull();
+ }
+
+ ALWAYS_INLINE bool operator!=(const ObjPtr& ptr) const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return Decode() != ptr.Decode();
+ }
+
+ ALWAYS_INLINE bool operator!=(const MirrorType* ptr) const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return Decode() != ptr;
+ }
+
+ ALWAYS_INLINE bool operator!=(std::nullptr_t) const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return !IsNull();
+ }
+
+ private:
+ // Trim off high bits of thread local cookie.
+ ALWAYS_INLINE static uintptr_t TrimCookie(uintptr_t cookie) {
+ return cookie & kCookieMask;
+ }
+
+ ALWAYS_INLINE uintptr_t GetCookie() const {
+ return reference_ >> kCookieShift;
+ }
+
+ ALWAYS_INLINE static uintptr_t Encode(MirrorType* ptr) REQUIRES_SHARED(Locks::mutator_lock_) {
+ uintptr_t ref = reinterpret_cast<uintptr_t>(ptr);
+ if (kPoison && ref != 0) {
+ DCHECK_LE(ref, 0xFFFFFFFFU);
+ ref >>= kObjectAlignmentShift;
+ // Put cookie in high bits.
+ Thread* self = Thread::Current();
+ DCHECK(self != nullptr);
+ ref |= self->GetPoisonObjectCookie() << kCookieShift;
+ }
+ return ref;
+ }
+
+ // Decode makes sure that the object pointer is valid.
+ ALWAYS_INLINE MirrorType* Decode() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ AssertValid();
+ if (kPoison) {
+ return reinterpret_cast<MirrorType*>(
+ static_cast<uintptr_t>(static_cast<uint32_t>(reference_ << kObjectAlignmentShift)));
+ } else {
+ return reinterpret_cast<MirrorType*>(reference_);
+ }
+ }
+
+ // The encoded reference and cookie.
+ uintptr_t reference_;
+};
+
+
+} // namespace mirror
+} // namespace art
+
+#endif // ART_RUNTIME_MIRROR_OBJ_PTR_H_
diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc
index afd6115..f4ecfb5 100644
--- a/runtime/mirror/object_test.cc
+++ b/runtime/mirror/object_test.cc
@@ -35,6 +35,7 @@
#include "gc/heap.h"
#include "handle_scope-inl.h"
#include "iftable-inl.h"
+#include "obj_ptr.h"
#include "object-inl.h"
#include "object_array-inl.h"
#include "scoped_thread_state_change.h"
@@ -738,5 +739,62 @@
EXPECT_NE(hash_code, 0);
}
+TEST_F(ObjectTest, ObjectPointer) {
+ ScopedObjectAccess soa(Thread::Current());
+ jobject jclass_loader = LoadDex("XandY");
+ StackHandleScope<2> hs(soa.Self());
+ ObjPtr<mirror::Object, /*kPoison*/ true> null_ptr;
+ EXPECT_TRUE(null_ptr.IsNull());
+ EXPECT_TRUE(null_ptr.IsValid());
+ EXPECT_TRUE(null_ptr.Get() == nullptr);
+ EXPECT_TRUE(null_ptr == nullptr);
+ EXPECT_TRUE(null_ptr == null_ptr);
+ EXPECT_FALSE(null_ptr != null_ptr);
+ EXPECT_FALSE(null_ptr != nullptr);
+ null_ptr.AssertValid();
+ Handle<ClassLoader> class_loader(hs.NewHandle(soa.Decode<ClassLoader*>(jclass_loader)));
+ Handle<mirror::Class> h_X(
+ hs.NewHandle(class_linker_->FindClass(soa.Self(), "LX;", class_loader)));
+ ObjPtr<Class, /*kPoison*/ true> X(h_X.Get());
+ EXPECT_TRUE(!X.IsNull());
+ EXPECT_TRUE(X.IsValid());
+ EXPECT_TRUE(X.Get() != nullptr);
+ EXPECT_EQ(h_X.Get(), X.Get());
+ // FindClass may cause thread suspension, it should invalidate X.
+ ObjPtr<Class, /*kPoison*/ true> Y(class_linker_->FindClass(soa.Self(), "LY;", class_loader));
+ EXPECT_TRUE(!Y.IsNull());
+ EXPECT_TRUE(Y.IsValid());
+ EXPECT_TRUE(Y.Get() != nullptr);
+
+ // Should IsNull be safe to call on null ObjPtr? I'll allow it for now.
+ EXPECT_TRUE(!X.IsNull());
+ EXPECT_TRUE(!X.IsValid());
+ // Make X valid again by copying out of handle.
+ X.Assign(h_X.Get());
+ EXPECT_TRUE(!X.IsNull());
+ EXPECT_TRUE(X.IsValid());
+ EXPECT_EQ(h_X.Get(), X.Get());
+
+ // Allow thread suspension to invalidate Y.
+ soa.Self()->AllowThreadSuspension();
+ EXPECT_TRUE(!Y.IsNull());
+ EXPECT_TRUE(!Y.IsValid());
+
+ // Test unpoisoned.
+ ObjPtr<mirror::Object, /*kPoison*/ false> unpoisoned;
+ EXPECT_TRUE(unpoisoned.IsNull());
+ EXPECT_TRUE(unpoisoned.IsValid());
+ EXPECT_TRUE(unpoisoned.Get() == nullptr);
+ EXPECT_TRUE(unpoisoned == nullptr);
+ EXPECT_TRUE(unpoisoned == unpoisoned);
+ EXPECT_FALSE(unpoisoned != unpoisoned);
+ EXPECT_FALSE(unpoisoned != nullptr);
+
+ unpoisoned = h_X.Get();
+ EXPECT_FALSE(unpoisoned.IsNull());
+ EXPECT_TRUE(unpoisoned == h_X.Get());
+ EXPECT_EQ(unpoisoned.Get(), h_X.Get());
+}
+
} // namespace mirror
} // namespace art
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 216d8a7..298a974 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -59,6 +59,7 @@
if (UNLIKELY(TestAllFlags())) {
CheckSuspend();
}
+ PoisonObjectPointers();
}
inline void Thread::CheckSuspend() {
diff --git a/runtime/thread.h b/runtime/thread.h
index 016c2bc..fb6bde6 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -471,6 +471,14 @@
}
void Notify() REQUIRES(!*wait_mutex_);
+ ALWAYS_INLINE void PoisonObjectPointers() {
+ ++poison_object_cookie_;
+ }
+
+ ALWAYS_INLINE uintptr_t GetPoisonObjectCookie() const {
+ return poison_object_cookie_;
+ }
+
private:
void NotifyLocked(Thread* self) REQUIRES(wait_mutex_);
@@ -1528,6 +1536,9 @@
// Debug disable read barrier count, only is checked for debug builds and only in the runtime.
uint8_t debug_disallow_read_barrier_ = 0;
+ // Note that it is not in the packed struct, may not be accessed for cross compilation.
+ uintptr_t poison_object_cookie_ = 0;
+
// Pending extra checkpoints if checkpoint_function_ is already used.
std::list<Closure*> checkpoint_overflow_ GUARDED_BY(Locks::thread_suspend_count_lock_);