Implement VMDebug API to mark a class "platform"

Add a native implementation of VMDebug.allowHiddenApiReflectionFrom
which takes a Class object and sets a new access flag
kAccSkipAccessChecks on the corresponding mirror::Class instance.

The function will throw a SecurityException if the process has not
been forked as Java debuggable.

hiddenapi::IsCallerInPlatformDex is extended to take an optional
argument of the mirror::Class of the caller. If it is set and
the class has kAccSkipAccessChecks, the caller is allowed to
acccess non-SDK APIs.

Note that the mirror::Class of the caller is only provided for
reflection and JNI. The access flag is ignored for other means of
access.

Bug: 64382372
Test: N/A
Change-Id: I2bf0ca7dcb45c17fe91eb2d421c947b892bd6fec
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index 2425a58..be82fff 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -58,6 +58,7 @@
 static constexpr uint32_t kAccSkipAccessChecks =      0x00080000;  // method (runtime, not native)
 // Used by a class to denote that the verifier has attempted to check it at least once.
 static constexpr uint32_t kAccVerificationAttempted = 0x00080000;  // class (runtime)
+static constexpr uint32_t kAccSkipHiddenApiChecks =   0x00100000;  // class (runtime)
 // This is set by the class linker during LinkInterfaceMethods. It is used by a method to represent
 // that it was copied from its declaring class into another class. All methods marked kAccMiranda
 // and kAccDefaultConflict will have this bit set. Any kAccDefault method contained in the methods_
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index 65c6406..8e21fd3 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -143,31 +143,45 @@
 // Returns true if the caller is either loaded by the boot strap class loader or comes from
 // a dex file located in ${ANDROID_ROOT}/framework/.
 ALWAYS_INLINE
-inline bool IsCallerInPlatformDex(ObjPtr<mirror::ClassLoader> caller_class_loader,
-                                  ObjPtr<mirror::DexCache> caller_dex_cache)
+inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller,
+                            ObjPtr<mirror::ClassLoader> caller_class_loader,
+                            ObjPtr<mirror::DexCache> caller_dex_cache)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (caller_class_loader.IsNull()) {
+    // Boot class loader.
     return true;
-  } else if (caller_dex_cache.IsNull()) {
-    return false;
-  } else {
-    const DexFile* caller_dex_file = caller_dex_cache->GetDexFile();
-    return caller_dex_file != nullptr && caller_dex_file->IsPlatformDexFile();
   }
+
+  if (!caller_dex_cache.IsNull()) {
+    const DexFile* caller_dex_file = caller_dex_cache->GetDexFile();
+    if (caller_dex_file != nullptr && caller_dex_file->IsPlatformDexFile()) {
+      // Caller is in a platform dex file.
+      return true;
+    }
+  }
+
+  if (!caller.IsNull() &&
+      caller->ShouldSkipHiddenApiChecks() &&
+      Runtime::Current()->IsJavaDebuggable()) {
+    // We are in debuggable mode and this caller has been marked trusted.
+    return true;
+  }
+
+  return false;
 }
 
 }  // namespace detail
 
 // Returns true if access to `member` should be denied to the caller of the
-// reflective query. The decision is based on whether the caller is in the
-// platform or not. Because different users of this function determine this
-// in a different way, `fn_caller_in_platform(self)` is called and should
-// return true if the caller is located in the platform.
+// reflective query. The decision is based on whether the caller is trusted or
+// not. Because different users of this function determine this in a different
+// way, `fn_caller_is_trusted(self)` is called and should return true if the
+// caller is allowed to access the platform.
 // This function might print warnings into the log if the member is hidden.
 template<typename T>
 inline Action GetMemberAction(T* member,
                               Thread* self,
-                              std::function<bool(Thread*)> fn_caller_in_platform,
+                              std::function<bool(Thread*)> fn_caller_is_trusted,
                               AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(member != nullptr);
@@ -188,8 +202,8 @@
 
   // Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access.
   // This can be *very* expensive. Save it for last.
-  if (fn_caller_in_platform(self)) {
-    // Caller in the platform. Exit.
+  if (fn_caller_is_trusted(self)) {
+    // Caller is trusted. Exit.
     return kAllow;
   }
 
@@ -197,10 +211,9 @@
   return detail::GetMemberActionImpl(member, api_list, action, access_method);
 }
 
-inline bool IsCallerInPlatformDex(ObjPtr<mirror::Class> caller)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
+inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller) REQUIRES_SHARED(Locks::mutator_lock_) {
   return !caller.IsNull() &&
-      detail::IsCallerInPlatformDex(caller->GetClassLoader(), caller->GetDexCache());
+      detail::IsCallerTrusted(caller, caller->GetClassLoader(), caller->GetDexCache());
 }
 
 // Returns true if access to `member` should be denied to a caller loaded with
@@ -212,10 +225,11 @@
                               ObjPtr<mirror::DexCache> caller_dex_cache,
                               AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  bool caller_in_platform = detail::IsCallerInPlatformDex(caller_class_loader, caller_dex_cache);
+  bool is_caller_trusted =
+      detail::IsCallerTrusted(/* caller */ nullptr, caller_class_loader, caller_dex_cache);
   return GetMemberAction(member,
                          /* thread */ nullptr,
-                         [caller_in_platform] (Thread*) { return caller_in_platform; },
+                         [is_caller_trusted] (Thread*) { return is_caller_trusted; },
                          access_method);
 }
 
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index 9dbcded..cd66a60 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -80,15 +80,15 @@
 // things not rendering correctly. E.g. b/16858794
 static constexpr bool kWarnJniAbort = false;
 
-static bool IsCallerInPlatformDex(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
-  return hiddenapi::IsCallerInPlatformDex(GetCallingClass(self, /* num_frames */ 1));
+static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
+  return hiddenapi::IsCallerTrusted(GetCallingClass(self, /* num_frames */ 1));
 }
 
 template<typename T>
 ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   hiddenapi::Action action = hiddenapi::GetMemberAction(
-      member, self, IsCallerInPlatformDex, hiddenapi::kJNI);
+      member, self, IsCallerTrusted, hiddenapi::kJNI);
   if (action != hiddenapi::kAllow) {
     hiddenapi::NotifyHiddenApiListener(member);
   }
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 51d1376..98e25eb 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -210,6 +210,15 @@
     return (GetAccessFlags() & kAccClassIsFinalizable) != 0;
   }
 
+  ALWAYS_INLINE bool ShouldSkipHiddenApiChecks() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return (GetAccessFlags() & kAccSkipHiddenApiChecks) != 0;
+  }
+
+  ALWAYS_INLINE void SetSkipHiddenApiChecks() REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint32_t flags = GetAccessFlags();
+    SetAccessFlags(flags | kAccSkipHiddenApiChecks);
+  }
+
   ALWAYS_INLINE void SetRecursivelyInitialized() REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK_EQ(GetLockOwnerThreadId(), Thread::Current()->GetThreadId());
     uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_));
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 6aaafc2..f1e267b 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -588,6 +588,25 @@
   Runtime::Current()->AttachAgent(env, filename, classloader);
 }
 
+static void VMDebug_allowHiddenApiReflectionFrom(JNIEnv* env, jclass, jclass j_caller) {
+  Runtime* runtime = Runtime::Current();
+  ScopedObjectAccess soa(env);
+
+  if (!runtime->IsJavaDebuggable()) {
+    ThrowSecurityException("Can't exempt class, process is not debuggable.");
+    return;
+  }
+
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Class> h_caller(hs.NewHandle(soa.Decode<mirror::Class>(j_caller)));
+  if (h_caller.IsNull()) {
+    ThrowNullPointerException("argument is null");
+    return;
+  }
+
+  h_caller->SetSkipHiddenApiChecks();
+}
+
 static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
   NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"),
@@ -623,6 +642,7 @@
   NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
   NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
   NATIVE_METHOD(VMDebug, nativeAttachAgent, "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"),
+  NATIVE_METHOD(VMDebug, allowHiddenApiReflectionFrom, "(Ljava/lang/Class;)V"),
 };
 
 void register_dalvik_system_VMDebug(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 2625c0a..68024cd 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -52,7 +52,7 @@
 
 // Returns true if the first caller outside of the Class class or java.lang.invoke package
 // is in a platform DEX file.
-static bool IsCallerInPlatformDex(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
+static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
   // Walk the stack and find the first frame not from java.lang.Class and not from java.lang.invoke.
   // This is very expensive. Save this till the last.
   struct FirstExternalCallerVisitor : public StackVisitor {
@@ -99,7 +99,7 @@
   FirstExternalCallerVisitor visitor(self);
   visitor.WalkStack();
   return visitor.caller != nullptr &&
-         hiddenapi::IsCallerInPlatformDex(visitor.caller->GetDeclaringClass());
+         hiddenapi::IsCallerTrusted(visitor.caller->GetDeclaringClass());
 }
 
 // Returns true if the first non-ClassClass caller up the stack is not allowed to
@@ -107,7 +107,7 @@
 ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   hiddenapi::EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
-  return policy != hiddenapi::EnforcementPolicy::kNoChecks && !IsCallerInPlatformDex(self);
+  return policy != hiddenapi::EnforcementPolicy::kNoChecks && !IsCallerTrusted(self);
 }
 
 // Returns true if the first non-ClassClass caller up the stack should not be
@@ -116,7 +116,7 @@
 ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   hiddenapi::Action action = hiddenapi::GetMemberAction(
-      member, self, IsCallerInPlatformDex, hiddenapi::kReflection);
+      member, self, IsCallerTrusted, hiddenapi::kReflection);
   if (action != hiddenapi::kAllow) {
     hiddenapi::NotifyHiddenApiListener(member);
   }