summaryrefslogtreecommitdiff
path: root/runtime/hidden_api.cc
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/hidden_api.cc')
-rw-r--r--runtime/hidden_api.cc420
1 files changed, 265 insertions, 155 deletions
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index 5729800bb0..1279997ba2 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -18,19 +18,17 @@
#include <nativehelper/scoped_local_ref.h>
+#include "art_field-inl.h"
+#include "art_method-inl.h"
#include "base/dumpable.h"
-#include "thread-current-inl.h"
+#include "class_root.h"
+#include "dex/class_accessor-inl.h"
+#include "dex/dex_file_loader.h"
+#include "mirror/class_ext.h"
+#include "scoped_thread_state_change.h"
+#include "thread-inl.h"
#include "well_known_classes.h"
-#ifdef ART_TARGET_ANDROID
-#include <metricslogger/metrics_logger.h>
-using android::metricslogger::ComplexEventLogger;
-using android::metricslogger::ACTION_HIDDEN_API_ACCESSED;
-using android::metricslogger::FIELD_HIDDEN_API_ACCESS_METHOD;
-using android::metricslogger::FIELD_HIDDEN_API_ACCESS_DENIED;
-using android::metricslogger::FIELD_HIDDEN_API_SIGNATURE;
-#endif
-
namespace art {
namespace hiddenapi {
@@ -44,38 +42,46 @@ static constexpr bool kLogAllAccesses = false;
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, HiddenApiAccessFlags::ApiList apiList) {
- return static_cast<int>(policy) == static_cast<int>(apiList);
+static inline std::ostream& operator<<(std::ostream& os, const AccessContext& value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!value.GetClass().IsNull()) {
+ std::string tmp;
+ os << value.GetClass()->GetDescriptor(&tmp);
+ } else if (value.GetDexFile() != nullptr) {
+ os << value.GetDexFile()->GetLocation();
+ } else {
+ os << "<unknown_caller>";
+ }
+ return os;
}
-// GetMemberAction-related static_asserts.
-static_assert(
- EnumsEqual(EnforcementPolicy::kDarkGreyAndBlackList, HiddenApiAccessFlags::kDarkGreylist) &&
- EnumsEqual(EnforcementPolicy::kBlacklistOnly, HiddenApiAccessFlags::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,
+};
+
MemberSignature::MemberSignature(ArtField* field) {
class_name_ = field->GetDeclaringClass()->GetDescriptor(&tmp_);
member_name_ = field->GetName();
@@ -94,6 +100,24 @@ MemberSignature::MemberSignature(ArtMethod* method) {
type_ = kMethod;
}
+MemberSignature::MemberSignature(const ClassAccessor::Field& field) {
+ const DexFile& dex_file = field.GetDexFile();
+ const dex::FieldId& field_id = dex_file.GetFieldId(field.GetIndex());
+ class_name_ = dex_file.GetFieldDeclaringClassDescriptor(field_id);
+ member_name_ = dex_file.GetFieldName(field_id);
+ type_signature_ = dex_file.GetFieldTypeDescriptor(field_id);
+ type_ = kField;
+}
+
+MemberSignature::MemberSignature(const ClassAccessor::Method& method) {
+ const DexFile& dex_file = method.GetDexFile();
+ const dex::MethodId& method_id = dex_file.GetMethodId(method.GetIndex());
+ class_name_ = dex_file.GetMethodDeclaringClassDescriptor(method_id);
+ member_name_ = dex_file.GetMethodName(method_id);
+ type_signature_ = dex_file.GetMethodSignature(method_id).ToString();
+ type_ = kMethod;
+}
+
inline std::vector<const char*> MemberSignature::GetSignatureParts() const {
if (type_ == kField) {
return { class_name_.c_str(), "->", member_name_.c_str(), ":", type_signature_.c_str() };
@@ -133,189 +157,275 @@ void MemberSignature::Dump(std::ostream& os) const {
}
}
-void MemberSignature::WarnAboutAccess(AccessMethod access_method,
- HiddenApiAccessFlags::ApiList list) {
+void MemberSignature::WarnAboutAccess(AccessMethod access_method, hiddenapi::ApiList list) {
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
-// prevents coupling the internal enum to the proto enum (which should never
-// be changed) so that we are free to change the internal one if necessary in
-// future.
-inline static int32_t GetEnumValueForLog(AccessMethod access_method) {
- switch (access_method) {
- case kNone:
- return android::metricslogger::ACCESS_METHOD_NONE;
- case kReflection:
- return android::metricslogger::ACCESS_METHOD_REFLECTION;
- case kJNI:
- return android::metricslogger::ACCESS_METHOD_JNI;
- case kLinking:
- return android::metricslogger::ACCESS_METHOD_LINKING;
- default:
- DCHECK(false);
- }
+
+bool MemberSignature::Equals(const MemberSignature& other) {
+ return type_ == other.type_ &&
+ class_name_ == other.class_name_ &&
+ member_name_ == other.member_name_ &&
+ type_signature_ == other.type_signature_;
+}
+
+bool MemberSignature::MemberNameAndTypeMatch(const MemberSignature& other) {
+ return member_name_ == other.member_name_ && type_signature_ == other.type_signature_;
}
-#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.
// None does not correspond to actual access, so should also be ignored.
return;
}
- ComplexEventLogger log_maker(ACTION_HIDDEN_API_ACCESSED);
- log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_METHOD, GetEnumValueForLog(access_method));
- if (action_taken == kDeny) {
- log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_DENIED, 1);
+ Runtime* runtime = Runtime::Current();
+ if (runtime->IsAotCompiler()) {
+ return;
}
+ JNIEnvExt* env = Thread::Current()->GetJniEnv();
const std::string& package_name = Runtime::Current()->GetProcessPackageName();
- if (!package_name.empty()) {
- log_maker.SetPackageName(package_name);
+ ScopedLocalRef<jstring> package_str(env, env->NewStringUTF(package_name.c_str()));
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ LOG(ERROR) << "Unable to allocate string for package name which called hidden api";
}
std::ostringstream signature_str;
Dump(signature_str);
- log_maker.AddTaggedData(FIELD_HIDDEN_API_SIGNATURE, signature_str.str());
- log_maker.Record();
+ ScopedLocalRef<jstring> signature_jstr(env,
+ env->NewStringUTF(signature_str.str().c_str()));
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ LOG(ERROR) << "Unable to allocate string for hidden api method signature";
+ }
+ env->CallStaticVoidMethod(WellKnownClasses::dalvik_system_VMRuntime,
+ WellKnownClasses::dalvik_system_VMRuntime_hiddenApiUsed, package_str.get(),
+ signature_jstr.get(), static_cast<jint>(access_method), access_denied);
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ LOG(ERROR) << "Unable to report hidden api usage";
+ }
#else
UNUSED(access_method);
- UNUSED(action_taken);
+ UNUSED(access_denied);
#endif
}
-static ALWAYS_INLINE bool CanUpdateMemberAccessFlags(ArtField*) {
+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 CanUpdateRuntimeFlags(ArtField*) {
return true;
}
-static ALWAYS_INLINE bool CanUpdateMemberAccessFlags(ArtMethod* method) {
+static ALWAYS_INLINE bool CanUpdateRuntimeFlags(ArtMethod* method) {
return !method->IsIntrinsic();
}
template<typename T>
static ALWAYS_INLINE void MaybeWhitelistMember(Runtime* runtime, T* member)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (CanUpdateMemberAccessFlags(member) && runtime->ShouldDedupeHiddenApiWarnings()) {
- member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime(
- member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist));
+ if (CanUpdateRuntimeFlags(member) && runtime->ShouldDedupeHiddenApiWarnings()) {
+ member->SetAccessFlags(member->GetAccessFlags() | kAccPublicApi);
}
}
-template<typename T>
-Action GetMemberActionImpl(T* member,
- HiddenApiAccessFlags::ApiList api_list,
- Action action,
- AccessMethod access_method) {
- DCHECK_NE(action, kAllow);
+static ALWAYS_INLINE uint32_t GetMemberDexIndex(ArtField* field) {
+ return field->GetDexFieldIndex();
+}
- // Get the signature, we need it later.
- MemberSignature member_signature(member);
+static ALWAYS_INLINE uint32_t GetMemberDexIndex(ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Use the non-obsolete method to avoid DexFile mismatch between
+ // the method index and the declaring class.
+ return method->GetNonObsoleteMethod()->GetDexMethodIndex();
+}
- Runtime* runtime = Runtime::Current();
+static void VisitMembers(const DexFile& dex_file,
+ const dex::ClassDef& class_def,
+ const std::function<void(const ClassAccessor::Field&)>& fn_visit) {
+ ClassAccessor accessor(dex_file, class_def, /* parse_hiddenapi_class_data= */ true);
+ accessor.VisitFields(fn_visit, fn_visit);
+}
- // 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;
- }
+static void VisitMembers(const DexFile& dex_file,
+ const dex::ClassDef& class_def,
+ const std::function<void(const ClassAccessor::Method&)>& fn_visit) {
+ ClassAccessor accessor(dex_file, class_def, /* parse_hiddenapi_class_data= */ true);
+ accessor.VisitMethods(fn_visit, fn_visit);
+}
- 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.
- member_signature.WarnAboutAccess(access_method, api_list);
- }
+template<typename T>
+uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_) {
+ static_assert(std::is_same<T, ArtField>::value || std::is_same<T, ArtMethod>::value);
+ using AccessorType = typename std::conditional<std::is_same<T, ArtField>::value,
+ ClassAccessor::Field, ClassAccessor::Method>::type;
+
+ ObjPtr<mirror::Class> declaring_class = member->GetDeclaringClass();
+ DCHECK(!declaring_class.IsNull()) << "Attempting to access a runtime method";
+
+ ApiList flags;
+ DCHECK(!flags.IsValid());
+
+ // Check if the declaring class has ClassExt allocated. If it does, check if
+ // the pre-JVMTI redefine dex file has been set to determine if the declaring
+ // class has been JVMTI-redefined.
+ ObjPtr<mirror::ClassExt> ext(declaring_class->GetExtData());
+ const DexFile* original_dex = ext.IsNull() ? nullptr : ext->GetPreRedefineDexFile();
+ if (LIKELY(original_dex == nullptr)) {
+ // Class is not redefined. Find the class def, iterate over its members and
+ // find the entry corresponding to this `member`.
+ const dex::ClassDef* class_def = declaring_class->GetClassDef();
+ DCHECK(class_def != nullptr) << "Class def should always be set for initialized classes";
+
+ uint32_t member_index = GetMemberDexIndex(member);
+ auto fn_visit = [&](const AccessorType& dex_member) {
+ if (dex_member.GetIndex() == member_index) {
+ flags = ApiList(dex_member.GetHiddenapiFlags());
+ }
+ };
+ VisitMembers(declaring_class->GetDexFile(), *class_def, fn_visit);
+ } else {
+ // Class was redefined using JVMTI. We have a pointer to the original dex file
+ // and the class def index of this class in that dex file, but the field/method
+ // indices are lost. Iterate over all members of the class def and find the one
+ // corresponding to this `member` by name and type string comparison.
+ // This is obviously very slow, but it is only used when non-exempt code tries
+ // to access a hidden member of a JVMTI-redefined class.
+ uint16_t class_def_idx = ext->GetPreRedefineClassDefIndex();
+ DCHECK_NE(class_def_idx, DexFile::kDexNoIndex16);
+ const dex::ClassDef& original_class_def = original_dex->GetClassDef(class_def_idx);
+ MemberSignature member_signature(member);
+ auto fn_visit = [&](const AccessorType& dex_member) {
+ MemberSignature cur_signature(dex_member);
+ if (member_signature.MemberNameAndTypeMatch(cur_signature)) {
+ DCHECK(member_signature.Equals(cur_signature));
+ flags = ApiList(dex_member.GetHiddenapiFlags());
+ }
+ };
+ VisitMembers(*original_dex, original_class_def, fn_visit);
}
- 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);
- }
- }
+ CHECK(flags.IsValid()) << "Could not find hiddenapi flags for "
+ << Dumpable<MemberSignature>(MemberSignature(member));
+ return flags.GetDexFlags();
+}
- if (action == kDeny) {
- // Block access
- return action;
+template<typename T>
+void MaybeReportCorePlatformApiViolation(T* member,
+ const AccessContext& caller_context,
+ AccessMethod access_method) {
+ if (access_method != AccessMethod::kNone) {
+ MemberSignature sig(member);
+ LOG(ERROR) << "CorePlatformApi violation: " << Dumpable<MemberSignature>(sig)
+ << " from " << caller_context << " using " << access_method;
}
+}
- // 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);
+template<typename T>
+bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
+ DCHECK(member != nullptr);
+ Runtime* runtime = Runtime::Current();
- // If this action requires a UI warning, set the appropriate flag.
- if (shouldWarn &&
- (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
- runtime->SetPendingHiddenApiWarning(true);
- }
- }
+ EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy();
+ DCHECK(policy != EnforcementPolicy::kDisabled)
+ << "Should never enter this function when access checks are completely disabled";
- return action;
-}
+ const bool deny_access =
+ (policy == EnforcementPolicy::kEnabled) &&
+ IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(),
+ api_list.GetMaxAllowedSdkVersion());
-// Need to instantiate this.
-template Action GetMemberActionImpl<ArtField>(ArtField* member,
- HiddenApiAccessFlags::ApiList api_list,
- Action action,
- AccessMethod access_method);
-template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member,
- HiddenApiAccessFlags::ApiList api_list,
- Action action,
- AccessMethod access_method);
-} // namespace detail
+ MemberSignature member_signature(member);
-template<typename T>
-void NotifyHiddenApiListener(T* member) {
- Runtime* runtime = Runtime::Current();
- if (!runtime->IsAotCompiler()) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
+ // Check for an exemption first. Exempted APIs are treated as white list.
+ 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;
+ }
- 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);
+ 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);
+ }
- ScopedLocalRef<jobject> signature_str(
- soa.Env(),
- soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
+ // 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);
+ }
+ }
- // Call through to Consumer.accept(String memberSignature);
- soa.Env()->CallVoidMethod(consumer_object.get(),
- WellKnownClasses::java_util_function_Consumer_accept,
- signature_str.get());
+ // 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);
}
}
+
+ return deny_access;
}
-template void NotifyHiddenApiListener<ArtMethod>(ArtMethod* member);
-template void NotifyHiddenApiListener<ArtField>(ArtField* member);
+// Need to instantiate these.
+template uint32_t GetDexFlags<ArtField>(ArtField* member);
+template uint32_t GetDexFlags<ArtMethod>(ArtMethod* member);
+template void MaybeReportCorePlatformApiViolation(ArtField* member,
+ const AccessContext& caller_context,
+ AccessMethod access_method);
+template void MaybeReportCorePlatformApiViolation(ArtMethod* member,
+ const AccessContext& caller_context,
+ AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtField>(ArtField* member,
+ ApiList api_list,
+ AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtMethod>(ArtMethod* member,
+ ApiList api_list,
+ AccessMethod access_method);
+} // namespace detail
} // namespace hiddenapi
} // namespace art