Start enforcing hidden API blacklist
Insert checks into reflection, JNI and Class::CanAccessMember to
make blacklisted hidden APIs undiscoverable.
The change was tested with internal microbenchmarks of reflection
and those showed no measurable performance impact.
Test: art/test.py -b -r -t 674-hiddenapi
Bug: 64382372
Change-Id: I9e39804b837ae9ffeba926f2a5b1e8e9986c472b
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 7b999c0..4d36e80 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -25,6 +25,7 @@
#include "common_throws.h"
#include "dex/dex_file-inl.h"
#include "dex/dex_file_annotations.h"
+#include "hidden_api.h"
#include "jni_internal.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
@@ -47,6 +48,75 @@
namespace art {
+ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!Runtime::Current()->AreHiddenApiChecksEnabled()) {
+ return false;
+ }
+
+ // Walk the stack and find the first frame not from java.lang.Class.
+ // This is very expensive. Save this till the last.
+ struct FirstNonClassClassCallerVisitor : public StackVisitor {
+ explicit FirstNonClassClassCallerVisitor(Thread* thread)
+ : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ caller(nullptr) {
+ }
+
+ bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) {
+ ArtMethod *m = GetMethod();
+ if (m == nullptr) {
+ // Attached native thread. Assume this is *not* boot class path.
+ caller = nullptr;
+ return false;
+ } else if (m->IsRuntimeMethod()) {
+ // Internal runtime method, continue walking the stack.
+ return true;
+ } else if (m->GetDeclaringClass()->IsClassClass()) {
+ return true;
+ } else {
+ caller = m;
+ return false;
+ }
+ }
+
+ ArtMethod* caller;
+ };
+
+ FirstNonClassClassCallerVisitor visitor(self);
+ visitor.WalkStack();
+ return visitor.caller == nullptr ||
+ !visitor.caller->GetDeclaringClass()->IsBootStrapClassLoaded();
+}
+
+// Returns true if the first non-ClassClass caller up the stack should not be
+// allowed access to `member`.
+template<typename T>
+ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(member != nullptr);
+ return hiddenapi::IsMemberHidden(member->GetAccessFlags()) &&
+ ShouldEnforceHiddenApi(self);
+}
+
+// Returns true if a class member should be discoverable with reflection given
+// the criteria. Some reflection calls only return public members
+// (public_only == true), some members should be hidden from non-boot class path
+// callers (enforce_hidden_api == true).
+ALWAYS_INLINE static bool IsDiscoverable(bool public_only,
+ bool enforce_hidden_api,
+ uint32_t access_flags) {
+ if (public_only && ((access_flags & kAccPublic) == 0)) {
+ return false;
+ }
+
+ if (enforce_hidden_api && hiddenapi::IsMemberHidden(access_flags)) {
+ return false;
+ }
+
+ return true;
+}
+
+
ALWAYS_INLINE static inline ObjPtr<mirror::Class> DecodeClass(
const ScopedFastNativeObjectAccess& soa, jobject java_class)
REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -164,17 +234,16 @@
IterationRange<StrideIterator<ArtField>> ifields = klass->GetIFields();
IterationRange<StrideIterator<ArtField>> sfields = klass->GetSFields();
size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields();
- if (public_only) {
- // Lets go subtract all the non public fields.
- for (ArtField& field : ifields) {
- if (!field.IsPublic()) {
- --array_size;
- }
+ bool enforce_hidden_api = ShouldEnforceHiddenApi(self);
+ // Lets go subtract all the non discoverable fields.
+ for (ArtField& field : ifields) {
+ if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
+ --array_size;
}
- for (ArtField& field : sfields) {
- if (!field.IsPublic()) {
- --array_size;
- }
+ }
+ for (ArtField& field : sfields) {
+ if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
+ --array_size;
}
}
size_t array_idx = 0;
@@ -184,7 +253,7 @@
return nullptr;
}
for (ArtField& field : ifields) {
- if (!public_only || field.IsPublic()) {
+ if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self,
&field,
force_resolve);
@@ -199,7 +268,7 @@
}
}
for (ArtField& field : sfields) {
- if (!public_only || field.IsPublic()) {
+ if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) {
auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self,
&field,
force_resolve);
@@ -354,8 +423,13 @@
ThrowNullPointerException("name == null");
return nullptr;
}
- return soa.AddLocalReference<jobject>(
- GetPublicFieldRecursive(soa.Self(), DecodeClass(soa, javaThis), name_string));
+
+ mirror::Field* field = GetPublicFieldRecursive(
+ soa.Self(), DecodeClass(soa, javaThis), name_string);
+ if (field == nullptr || ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) {
+ return nullptr;
+ }
+ return soa.AddLocalReference<jobject>(field);
}
static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring name) {
@@ -369,7 +443,7 @@
Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
Handle<mirror::Field> result =
hs.NewHandle(GetDeclaredField(soa.Self(), h_klass.Get(), h_string.Get()));
- if (result == nullptr) {
+ if (result == nullptr || ShouldBlockAccessToMember(result->GetArtField(), soa.Self())) {
std::string name_str = h_string->ToModifiedUtf8();
if (name_str == "value" && h_klass->IsStringClass()) {
// We log the error for this specific case, as the user might just swallow the exception.
@@ -399,24 +473,32 @@
soa.Self(),
DecodeClass(soa, javaThis),
soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+ if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
+ return nullptr;
+ }
return soa.AddLocalReference<jobject>(result);
}
-static ALWAYS_INLINE inline bool MethodMatchesConstructor(ArtMethod* m, bool public_only)
+static ALWAYS_INLINE inline bool MethodMatchesConstructor(
+ ArtMethod* m, bool public_only, bool enforce_hidden_api)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(m != nullptr);
- return (!public_only || m->IsPublic()) && !m->IsStatic() && m->IsConstructor();
+ return m->IsConstructor() &&
+ !m->IsStatic() &&
+ IsDiscoverable(public_only, enforce_hidden_api, m->GetAccessFlags());
}
static jobjectArray Class_getDeclaredConstructorsInternal(
JNIEnv* env, jobject javaThis, jboolean publicOnly) {
ScopedFastNativeObjectAccess soa(env);
StackHandleScope<2> hs(soa.Self());
+ bool public_only = (publicOnly != JNI_FALSE);
+ bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self());
Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
size_t constructor_count = 0;
// Two pass approach for speed.
for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
- constructor_count += MethodMatchesConstructor(&m, publicOnly != JNI_FALSE) ? 1u : 0u;
+ constructor_count += MethodMatchesConstructor(&m, public_only, enforce_hidden_api) ? 1u : 0u;
}
auto h_constructors = hs.NewHandle(mirror::ObjectArray<mirror::Constructor>::Alloc(
soa.Self(), mirror::Constructor::ArrayClass(), constructor_count));
@@ -426,7 +508,7 @@
}
constructor_count = 0;
for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
- if (MethodMatchesConstructor(&m, publicOnly != JNI_FALSE)) {
+ if (MethodMatchesConstructor(&m, public_only, enforce_hidden_api)) {
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
DCHECK(!Runtime::Current()->IsActiveTransaction());
auto* constructor = mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>(
@@ -452,6 +534,9 @@
DecodeClass(soa, javaThis),
soa.Decode<mirror::String>(name),
soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+ if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
+ return nullptr;
+ }
return soa.AddLocalReference<jobject>(result);
}
@@ -459,13 +544,17 @@
jboolean publicOnly) {
ScopedFastNativeObjectAccess soa(env);
StackHandleScope<2> hs(soa.Self());
+
+ bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self());
+ bool public_only = (publicOnly != JNI_FALSE);
+
Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
size_t num_methods = 0;
- for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
- auto modifiers = m.GetAccessFlags();
+ for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
+ uint32_t modifiers = m.GetAccessFlags();
// Add non-constructor declared methods.
- if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) &&
- (modifiers & kAccConstructor) == 0) {
+ if ((modifiers & kAccConstructor) == 0 &&
+ IsDiscoverable(public_only, enforce_hidden_api, modifiers)) {
++num_methods;
}
}
@@ -476,10 +565,10 @@
return nullptr;
}
num_methods = 0;
- for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
- auto modifiers = m.GetAccessFlags();
- if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) &&
- (modifiers & kAccConstructor) == 0) {
+ for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
+ uint32_t modifiers = m.GetAccessFlags();
+ if ((modifiers & kAccConstructor) == 0 &&
+ IsDiscoverable(public_only, enforce_hidden_api, modifiers)) {
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
DCHECK(!Runtime::Current()->IsActiveTransaction());
auto* method =
@@ -693,11 +782,11 @@
return nullptr;
}
}
- auto* constructor = klass->GetDeclaredConstructor(
+ ArtMethod* constructor = klass->GetDeclaredConstructor(
soa.Self(),
ScopedNullHandle<mirror::ObjectArray<mirror::Class>>(),
kRuntimePointerSize);
- if (UNLIKELY(constructor == nullptr)) {
+ if (UNLIKELY(constructor == nullptr) || ShouldBlockAccessToMember(constructor, soa.Self())) {
soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;",
"%s has no zero argument constructor",
klass->PrettyClass().c_str());