diff options
Diffstat (limited to 'runtime/hidden_api.cc')
-rw-r--r-- | runtime/hidden_api.cc | 420 |
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 |