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);
}