Simplify hidden_api.h logic
Refactor GetMemberAction to return a boolean whether access to a class
member should be denied. This also moves StrictMode consumer
notification into hidden_api.cc and removes notifications for toasts.
Tests are changed accordingly.
Test: phone boots
Test: m test-art
Merged-In: I02902143de0ff91d402ba79c83f28226b1822a6f
Change-Id: I02902143de0ff91d402ba79c83f28226b1822a6f
(cherry picked from commit 51995f90adaa0e5047dee56d22f15e4225e70517)
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index f355276..3b7b938 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -44,38 +44,53 @@
static inline std::ostream& operator<<(std::ostream& os, AccessMethod value) {
switch (value) {
- case kNone:
+ case AccessMethod::kNone:
LOG(FATAL) << "Internal access to hidden API should not be logged";
UNREACHABLE();
- case kReflection:
+ case AccessMethod::kReflection:
os << "reflection";
break;
- case kJNI:
+ case AccessMethod::kJNI:
os << "JNI";
break;
- case kLinking:
+ case AccessMethod::kLinking:
os << "linking";
break;
}
return os;
}
-static constexpr bool EnumsEqual(EnforcementPolicy policy, hiddenapi::ApiList apiList) {
- return static_cast<int>(policy) == static_cast<int>(apiList);
-}
-
-// GetMemberAction-related static_asserts.
-static_assert(
- EnumsEqual(EnforcementPolicy::kDarkGreyAndBlackList, hiddenapi::ApiList::kDarkGreylist) &&
- EnumsEqual(EnforcementPolicy::kBlacklistOnly, hiddenapi::ApiList::kBlacklist),
- "Mismatch between EnforcementPolicy and ApiList enums");
-static_assert(
- EnforcementPolicy::kJustWarn < EnforcementPolicy::kDarkGreyAndBlackList &&
- EnforcementPolicy::kDarkGreyAndBlackList < EnforcementPolicy::kBlacklistOnly,
- "EnforcementPolicy values ordering not correct");
-
namespace detail {
+// Do not change the values of items in this enum, as they are written to the
+// event log for offline analysis. Any changes will interfere with that analysis.
+enum AccessContextFlags {
+ // Accessed member is a field if this bit is set, else a method
+ kMemberIsField = 1 << 0,
+ // Indicates if access was denied to the member, instead of just printing a warning.
+ kAccessDenied = 1 << 1,
+};
+
+static int32_t GetMaxAllowedSdkVersionForApiList(ApiList api_list) {
+ SdkCodes sdk = SdkCodes::kVersionNone;
+ switch (api_list) {
+ case ApiList::kWhitelist:
+ case ApiList::kLightGreylist:
+ sdk = SdkCodes::kVersionUnlimited;
+ break;
+ case ApiList::kDarkGreylist:
+ sdk = SdkCodes::kVersionO_MR1;
+ break;
+ case ApiList::kBlacklist:
+ sdk = SdkCodes::kVersionNone;
+ break;
+ case ApiList::kNoList:
+ LOG(FATAL) << "Unexpected value";
+ UNREACHABLE();
+ }
+ return static_cast<int32_t>(sdk);
+}
+
MemberSignature::MemberSignature(ArtField* field) {
class_name_ = field->GetDeclaringClass()->GetDescriptor(&tmp_);
member_name_ = field->GetName();
@@ -137,6 +152,7 @@
LOG(WARNING) << "Accessing hidden " << (type_ == kField ? "field " : "method ")
<< Dumpable<MemberSignature>(*this) << " (" << list << ", " << access_method << ")";
}
+
#ifdef ART_TARGET_ANDROID
// Convert an AccessMethod enum to a value for logging from the proto enum.
// This method may look odd (the enum values are current the same), but it
@@ -145,13 +161,13 @@
// future.
inline static int32_t GetEnumValueForLog(AccessMethod access_method) {
switch (access_method) {
- case kNone:
+ case AccessMethod::kNone:
return android::metricslogger::ACCESS_METHOD_NONE;
- case kReflection:
+ case AccessMethod::kReflection:
return android::metricslogger::ACCESS_METHOD_REFLECTION;
- case kJNI:
+ case AccessMethod::kJNI:
return android::metricslogger::ACCESS_METHOD_JNI;
- case kLinking:
+ case AccessMethod::kLinking:
return android::metricslogger::ACCESS_METHOD_LINKING;
default:
DCHECK(false);
@@ -159,9 +175,9 @@
}
#endif
-void MemberSignature::LogAccessToEventLog(AccessMethod access_method, Action action_taken) {
+void MemberSignature::LogAccessToEventLog(AccessMethod access_method, bool access_denied) {
#ifdef ART_TARGET_ANDROID
- if (access_method == kLinking || access_method == kNone) {
+ if (access_method == AccessMethod::kLinking || access_method == AccessMethod::kNone) {
// Linking warnings come from static analysis/compilation of the bytecode
// and can contain false positives (i.e. code that is never run). We choose
// not to log these in the event log.
@@ -170,7 +186,7 @@
}
ComplexEventLogger log_maker(ACTION_HIDDEN_API_ACCESSED);
log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_METHOD, GetEnumValueForLog(access_method));
- if (action_taken == kDeny) {
+ if (access_denied) {
log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_DENIED, 1);
}
const std::string& package_name = Runtime::Current()->GetProcessPackageName();
@@ -183,10 +199,42 @@
log_maker.Record();
#else
UNUSED(access_method);
- UNUSED(action_taken);
+ UNUSED(access_denied);
#endif
}
+void MemberSignature::NotifyHiddenApiListener(AccessMethod access_method) {
+ if (access_method != AccessMethod::kReflection && access_method != AccessMethod::kJNI) {
+ // We can only up-call into Java during reflection and JNI down-calls.
+ return;
+ }
+
+ Runtime* runtime = Runtime::Current();
+ if (!runtime->IsAotCompiler()) {
+ ScopedObjectAccessUnchecked soa(Thread::Current());
+
+ ScopedLocalRef<jobject> consumer_object(soa.Env(),
+ soa.Env()->GetStaticObjectField(
+ WellKnownClasses::dalvik_system_VMRuntime,
+ WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
+ // If the consumer is non-null, we call back to it to let it know that we
+ // have encountered an API that's in one of our lists.
+ if (consumer_object != nullptr) {
+ std::ostringstream member_signature_str;
+ Dump(member_signature_str);
+
+ ScopedLocalRef<jobject> signature_str(
+ soa.Env(),
+ soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
+
+ // Call through to Consumer.accept(String memberSignature);
+ soa.Env()->CallVoidMethod(consumer_object.get(),
+ WellKnownClasses::java_util_function_Consumer_accept,
+ signature_str.get());
+ }
+ }
+}
+
static ALWAYS_INLINE bool CanUpdateMemberAccessFlags(ArtField*) {
return true;
}
@@ -205,116 +253,68 @@
}
template<typename T>
-Action GetMemberActionImpl(T* member,
- hiddenapi::ApiList api_list,
- Action action,
- AccessMethod access_method) {
- DCHECK_NE(action, kAllow);
-
- // Get the signature, we need it later.
- MemberSignature member_signature(member);
+bool ShouldDenyAccessToMemberImpl(T* member,
+ hiddenapi::ApiList api_list,
+ AccessMethod access_method) {
+ DCHECK(member != nullptr);
Runtime* runtime = Runtime::Current();
+ EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy();
+
+ const bool deny_access =
+ (policy == EnforcementPolicy::kEnabled) &&
+ (runtime->GetTargetSdkVersion() > GetMaxAllowedSdkVersionForApiList(api_list));
+
+ MemberSignature member_signature(member);
// Check for an exemption first. Exempted APIs are treated as white list.
- // We only do this if we're about to deny, or if the app is debuggable. This is because:
- // - we only print a warning for light greylist violations for debuggable apps
- // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs.
- // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever
- // possible.
- const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
- if (shouldWarn || action == kDeny) {
- if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
- action = kAllow;
- // Avoid re-examining the exemption list next time.
- // Note this results in no warning for the member, which seems like what one would expect.
- // Exemptions effectively adds new members to the whitelist.
- MaybeWhitelistMember(runtime, member);
- return kAllow;
- }
+ if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
+ // Avoid re-examining the exemption list next time.
+ // Note this results in no warning for the member, which seems like what one would expect.
+ // Exemptions effectively adds new members to the whitelist.
+ MaybeWhitelistMember(runtime, member);
+ return false;
+ }
- if (access_method != kNone) {
- // Print a log message with information about this class member access.
- // We do this if we're about to block access, or the app is debuggable.
+ if (access_method != AccessMethod::kNone) {
+ // Print a log message with information about this class member access.
+ // We do this if we're about to deny access, or the app is debuggable.
+ if (kLogAllAccesses || deny_access || runtime->IsJavaDebuggable()) {
member_signature.WarnAboutAccess(access_method, api_list);
}
- }
- if (kIsTargetBuild && !kIsTargetLinux) {
- uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
- // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
- static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
- if (eventLogSampleRate != 0 &&
- (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
- member_signature.LogAccessToEventLog(access_method, action);
+ // If there is a StrictMode listener, notify it about this violation.
+ member_signature.NotifyHiddenApiListener(access_method);
+
+ // If event log sampling is enabled, report this violation.
+ if (kIsTargetBuild && !kIsTargetLinux) {
+ uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
+ // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
+ static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
+ if (eventLogSampleRate != 0 &&
+ (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
+ member_signature.LogAccessToEventLog(access_method, deny_access);
+ }
+ }
+
+ // If this access was not denied, move the member into whitelist and skip
+ // the warning the next time the member is accessed.
+ if (!deny_access) {
+ MaybeWhitelistMember(runtime, member);
}
}
- if (action == kDeny) {
- // Block access
- return action;
- }
-
- // Allow access to this member but print a warning.
- DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast);
-
- if (access_method != kNone) {
- // Depending on a runtime flag, we might move the member into whitelist and
- // skip the warning the next time the member is accessed.
- MaybeWhitelistMember(runtime, member);
-
- // If this action requires a UI warning, set the appropriate flag.
- if (shouldWarn &&
- (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
- runtime->SetPendingHiddenApiWarning(true);
- }
- }
-
- return action;
+ return deny_access;
}
// Need to instantiate this.
-template Action GetMemberActionImpl<ArtField>(ArtField* member,
- hiddenapi::ApiList api_list,
- Action action,
- AccessMethod access_method);
-template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member,
- hiddenapi::ApiList api_list,
- Action action,
- AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtField>(ArtField* member,
+ hiddenapi::ApiList api_list,
+ AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtMethod>(ArtMethod* member,
+ hiddenapi::ApiList api_list,
+ AccessMethod access_method);
} // namespace detail
-template<typename T>
-void NotifyHiddenApiListener(T* member) {
- Runtime* runtime = Runtime::Current();
- if (!runtime->IsAotCompiler()) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
-
- ScopedLocalRef<jobject> consumer_object(soa.Env(),
- soa.Env()->GetStaticObjectField(
- WellKnownClasses::dalvik_system_VMRuntime,
- WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
- // If the consumer is non-null, we call back to it to let it know that we
- // have encountered an API that's in one of our lists.
- if (consumer_object != nullptr) {
- detail::MemberSignature member_signature(member);
- std::ostringstream member_signature_str;
- member_signature.Dump(member_signature_str);
-
- ScopedLocalRef<jobject> signature_str(
- soa.Env(),
- soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
-
- // Call through to Consumer.accept(String memberSignature);
- soa.Env()->CallVoidMethod(consumer_object.get(),
- WellKnownClasses::java_util_function_Consumer_accept,
- signature_str.get());
- }
- }
-}
-
-template void NotifyHiddenApiListener<ArtMethod>(ArtMethod* member);
-template void NotifyHiddenApiListener<ArtField>(ArtField* member);
-
} // namespace hiddenapi
} // namespace art