diff options
Diffstat (limited to 'runtime/hidden_api.h')
-rw-r--r-- | runtime/hidden_api.h | 445 |
1 files changed, 314 insertions, 131 deletions
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h index 580224e439..0bdb5c86cf 100644 --- a/runtime/hidden_api.h +++ b/runtime/hidden_api.h @@ -17,10 +17,11 @@ #ifndef ART_RUNTIME_HIDDEN_API_H_ #define ART_RUNTIME_HIDDEN_API_H_ -#include "art_field-inl.h" -#include "art_method-inl.h" -#include "base/mutex.h" -#include "dex/hidden_api_access_flags.h" +#include "art_field.h" +#include "art_method.h" +#include "base/hiddenapi_flags.h" +#include "base/locks.h" +#include "intrinsics_enum.h" #include "mirror/class-inl.h" #include "reflection.h" #include "runtime.h" @@ -32,11 +33,10 @@ namespace hiddenapi { // This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in // frameworks/base/core/java/android/content/pm/ApplicationInfo.java enum class EnforcementPolicy { - kNoChecks = 0, + kDisabled = 0, kJustWarn = 1, // keep checks enabled, but allow everything (enables logging) - kDarkGreyAndBlackList = 2, // ban dark grey & blacklist - kBlacklistOnly = 3, // ban blacklist violations only - kMax = kBlacklistOnly, + kEnabled = 2, // ban dark grey & blacklist + kMax = kEnabled, }; inline EnforcementPolicy EnforcementPolicyFromInt(int api_policy_int) { @@ -45,55 +45,101 @@ inline EnforcementPolicy EnforcementPolicyFromInt(int api_policy_int) { return static_cast<EnforcementPolicy>(api_policy_int); } -enum Action { - kAllow, - kAllowButWarn, - kAllowButWarnAndToast, - kDeny +// Hidden API access method +// Thist must be kept in sync with VMRuntime.HiddenApiUsageLogger.ACCESS_METHOD_* +enum class AccessMethod { + kNone = 0, // internal test that does not correspond to an actual access by app + kReflection = 1, + kJNI = 2, + kLinking = 3, }; -enum AccessMethod { - kNone, // internal test that does not correspond to an actual access by app - kReflection, - kJNI, - kLinking, -}; - -// 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, -}; +// Represents the API domain of a caller/callee. +class AccessContext { + public: + // Initialize to either the fully-trusted or fully-untrusted domain. + explicit AccessContext(bool is_trusted) + : klass_(nullptr), + dex_file_(nullptr), + domain_(ComputeDomain(is_trusted)) {} + + // Initialize from class loader and dex file (via dex cache). + AccessContext(ObjPtr<mirror::ClassLoader> class_loader, ObjPtr<mirror::DexCache> dex_cache) + REQUIRES_SHARED(Locks::mutator_lock_) + : klass_(nullptr), + dex_file_(GetDexFileFromDexCache(dex_cache)), + domain_(ComputeDomain(class_loader, dex_file_)) {} + + // Initialize from Class. + explicit AccessContext(ObjPtr<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_) + : klass_(klass), + dex_file_(GetDexFileFromDexCache(klass->GetDexCache())), + domain_(ComputeDomain(klass, dex_file_)) {} + + ObjPtr<mirror::Class> GetClass() const { return klass_; } + const DexFile* GetDexFile() const { return dex_file_; } + Domain GetDomain() const { return domain_; } + + bool IsUntrustedDomain() const { return domain_ == Domain::kApplication; } + + // Returns true if this domain is always allowed to access the domain of `callee`. + bool CanAlwaysAccess(const AccessContext& callee) const { + return IsDomainMoreTrustedThan(domain_, callee.domain_); + } -inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) { - if (api_list == HiddenApiAccessFlags::kWhitelist) { - return kAllow; + private: + static const DexFile* GetDexFileFromDexCache(ObjPtr<mirror::DexCache> dex_cache) + REQUIRES_SHARED(Locks::mutator_lock_) { + return dex_cache.IsNull() ? nullptr : dex_cache->GetDexFile(); } - EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy(); - if (policy == EnforcementPolicy::kNoChecks) { - // Exit early. Nothing to enforce. - return kAllow; + static Domain ComputeDomain(bool is_trusted) { + return is_trusted ? Domain::kCorePlatform : Domain::kApplication; } - // if policy is "just warn", always warn. We returned above for whitelist APIs. - if (policy == EnforcementPolicy::kJustWarn) { - return kAllowButWarn; + static Domain ComputeDomain(ObjPtr<mirror::ClassLoader> class_loader, const DexFile* dex_file) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (dex_file == nullptr) { + return ComputeDomain(/* is_trusted= */ class_loader.IsNull()); + } + + Domain dex_domain = dex_file->GetHiddenapiDomain(); + if (class_loader.IsNull() && dex_domain == Domain::kApplication) { + // LOG(WARNING) << "DexFile " << dex_file->GetLocation() << " is in boot classpath " + // << "but is assigned untrusted domain"; + dex_domain = Domain::kPlatform; + } + return dex_domain; } - DCHECK(policy >= EnforcementPolicy::kDarkGreyAndBlackList); - // The logic below relies on equality of values in the enums EnforcementPolicy and - // HiddenApiAccessFlags::ApiList, and their ordering. Assertions are in hidden_api.cc. - if (static_cast<int>(policy) > static_cast<int>(api_list)) { - return api_list == HiddenApiAccessFlags::kDarkGreylist - ? kAllowButWarnAndToast - : kAllowButWarn; - } else { - return kDeny; + + static Domain ComputeDomain(ObjPtr<mirror::Class> klass, const DexFile* dex_file) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Check other aspects of the context. + Domain domain = ComputeDomain(klass->GetClassLoader(), dex_file); + + if (domain == Domain::kApplication && + klass->ShouldSkipHiddenApiChecks() && + Runtime::Current()->IsJavaDebuggable()) { + // Class is known, it is marked trusted and we are in debuggable mode. + domain = ComputeDomain(/* is_trusted= */ true); + } + + return domain; } -} + + // Pointer to declaring class of the caller/callee (null if not provided). + // This is not safe across GC but we're only using this class for passing + // information about the caller to the access check logic and never retain + // the AccessContext instance beyond that. + const ObjPtr<mirror::Class> klass_; + + // DexFile of the caller/callee (null if not provided). + const DexFile* const dex_file_; + + // Computed domain of the caller/callee. + const Domain domain_; +}; class ScopedHiddenApiEnforcementPolicySetting { public: @@ -134,9 +180,14 @@ class MemberSignature { public: explicit MemberSignature(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_); explicit MemberSignature(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); + explicit MemberSignature(const ClassAccessor::Field& field); + explicit MemberSignature(const ClassAccessor::Method& method); void Dump(std::ostream& os) const; + bool Equals(const MemberSignature& other); + bool MemberNameAndTypeMatch(const MemberSignature& other); + // Performs prefix match on this member. Since the full member signature is // composed of several parts, we match each part in turn (rather than // building the entire thing in memory and performing a simple prefix match) @@ -144,119 +195,251 @@ class MemberSignature { bool IsExempted(const std::vector<std::string>& exemptions); - void WarnAboutAccess(AccessMethod access_method, HiddenApiAccessFlags::ApiList list); + void WarnAboutAccess(AccessMethod access_method, ApiList list); + + void LogAccessToEventLog(AccessMethod access_method, bool access_denied); - void LogAccessToEventLog(AccessMethod access_method, Action action_taken); + // Calls back into managed code to notify VMRuntime.nonSdkApiUsageConsumer that + // |member| was accessed. This is usually called when an API is on the black, + // dark grey or light grey lists. Given that the callback can execute arbitrary + // code, a call to this method can result in thread suspension. + void NotifyHiddenApiListener(AccessMethod access_method); }; +// Locates hiddenapi flags for `field` in the corresponding dex file. +// NB: This is an O(N) operation, linear with the number of members in the class def. +template<typename T> +uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_); + template<typename T> -Action GetMemberActionImpl(T* member, - HiddenApiAccessFlags::ApiList api_list, - Action action, - AccessMethod access_method) +void MaybeReportCorePlatformApiViolation(T* member, + const AccessContext& caller_context, + AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_); -// 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 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; - } +template<typename T> +bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) + REQUIRES_SHARED(Locks::mutator_lock_); + +} // namespace detail - 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; +// Returns access flags for the runtime representation of a class member (ArtField/ArtMember). +ALWAYS_INLINE inline uint32_t CreateRuntimeFlags(const ClassAccessor::BaseItem& member) { + uint32_t runtime_flags = 0u; + + ApiList api_list(member.GetHiddenapiFlags()); + DCHECK(api_list.IsValid()); + + if (api_list.Contains(ApiList::Whitelist())) { + runtime_flags |= kAccPublicApi; + } else { + // Only add domain-specific flags for non-public API members. + // This simplifies hardcoded values for intrinsics. + if (api_list.Contains(ApiList::CorePlatformApi())) { + runtime_flags |= kAccCorePlatformApi; } } - if (!caller.IsNull() && - caller->ShouldSkipHiddenApiChecks() && - Runtime::Current()->IsJavaDebuggable()) { - // We are in debuggable mode and this caller has been marked trusted. - return true; - } + DCHECK_EQ(runtime_flags & kAccHiddenapiBits, runtime_flags) + << "Runtime flags not in reserved access flags bits"; + return runtime_flags; +} - return false; +// Extracts hiddenapi runtime flags from access flags of ArtField. +ALWAYS_INLINE inline uint32_t GetRuntimeFlags(ArtField* field) + REQUIRES_SHARED(Locks::mutator_lock_) { + return field->GetAccessFlags() & kAccHiddenapiBits; } -} // namespace detail +// Extracts hiddenapi runtime flags from access flags of ArtMethod. +// Uses hardcoded values for intrinsics. +ALWAYS_INLINE inline uint32_t GetRuntimeFlags(ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (UNLIKELY(method->IsIntrinsic())) { + switch (static_cast<Intrinsics>(method->GetIntrinsic())) { + case Intrinsics::kSystemArrayCopyChar: + case Intrinsics::kStringGetCharsNoCheck: + case Intrinsics::kReferenceGetReferent: + case Intrinsics::kMemoryPeekByte: + case Intrinsics::kMemoryPokeByte: + case Intrinsics::kUnsafeCASInt: + case Intrinsics::kUnsafeCASLong: + case Intrinsics::kUnsafeCASObject: + case Intrinsics::kUnsafeGet: + case Intrinsics::kUnsafeGetAndAddInt: + case Intrinsics::kUnsafeGetAndAddLong: + case Intrinsics::kUnsafeGetAndSetInt: + case Intrinsics::kUnsafeGetAndSetLong: + case Intrinsics::kUnsafeGetAndSetObject: + case Intrinsics::kUnsafeGetLong: + case Intrinsics::kUnsafeGetLongVolatile: + case Intrinsics::kUnsafeGetObject: + case Intrinsics::kUnsafeGetObjectVolatile: + case Intrinsics::kUnsafeGetVolatile: + case Intrinsics::kUnsafePut: + case Intrinsics::kUnsafePutLong: + case Intrinsics::kUnsafePutLongOrdered: + case Intrinsics::kUnsafePutLongVolatile: + case Intrinsics::kUnsafePutObject: + case Intrinsics::kUnsafePutObjectOrdered: + case Intrinsics::kUnsafePutObjectVolatile: + case Intrinsics::kUnsafePutOrdered: + case Intrinsics::kUnsafePutVolatile: + case Intrinsics::kUnsafeLoadFence: + case Intrinsics::kUnsafeStoreFence: + case Intrinsics::kUnsafeFullFence: + case Intrinsics::kCRC32Update: + case Intrinsics::kCRC32UpdateBytes: + case Intrinsics::kCRC32UpdateByteBuffer: + case Intrinsics::kStringNewStringFromBytes: + case Intrinsics::kStringNewStringFromChars: + case Intrinsics::kStringNewStringFromString: + case Intrinsics::kMemoryPeekIntNative: + case Intrinsics::kMemoryPeekLongNative: + case Intrinsics::kMemoryPeekShortNative: + case Intrinsics::kMemoryPokeIntNative: + case Intrinsics::kMemoryPokeLongNative: + case Intrinsics::kMemoryPokeShortNative: + case Intrinsics::kVarHandleFullFence: + case Intrinsics::kVarHandleAcquireFence: + case Intrinsics::kVarHandleReleaseFence: + case Intrinsics::kVarHandleLoadLoadFence: + case Intrinsics::kVarHandleStoreStoreFence: + case Intrinsics::kVarHandleCompareAndExchange: + case Intrinsics::kVarHandleCompareAndExchangeAcquire: + case Intrinsics::kVarHandleCompareAndExchangeRelease: + case Intrinsics::kVarHandleCompareAndSet: + case Intrinsics::kVarHandleGet: + case Intrinsics::kVarHandleGetAcquire: + case Intrinsics::kVarHandleGetAndAdd: + case Intrinsics::kVarHandleGetAndAddAcquire: + case Intrinsics::kVarHandleGetAndAddRelease: + case Intrinsics::kVarHandleGetAndBitwiseAnd: + case Intrinsics::kVarHandleGetAndBitwiseAndAcquire: + case Intrinsics::kVarHandleGetAndBitwiseAndRelease: + case Intrinsics::kVarHandleGetAndBitwiseOr: + case Intrinsics::kVarHandleGetAndBitwiseOrAcquire: + case Intrinsics::kVarHandleGetAndBitwiseOrRelease: + case Intrinsics::kVarHandleGetAndBitwiseXor: + case Intrinsics::kVarHandleGetAndBitwiseXorAcquire: + case Intrinsics::kVarHandleGetAndBitwiseXorRelease: + case Intrinsics::kVarHandleGetAndSet: + case Intrinsics::kVarHandleGetAndSetAcquire: + case Intrinsics::kVarHandleGetAndSetRelease: + case Intrinsics::kVarHandleGetOpaque: + case Intrinsics::kVarHandleGetVolatile: + case Intrinsics::kVarHandleSet: + case Intrinsics::kVarHandleSetOpaque: + case Intrinsics::kVarHandleSetRelease: + case Intrinsics::kVarHandleSetVolatile: + case Intrinsics::kVarHandleWeakCompareAndSet: + case Intrinsics::kVarHandleWeakCompareAndSetAcquire: + case Intrinsics::kVarHandleWeakCompareAndSetPlain: + case Intrinsics::kVarHandleWeakCompareAndSetRelease: + return 0u; + default: + // Remaining intrinsics are public API. We DCHECK that in SetIntrinsic(). + return kAccPublicApi; + } + } else { + return method->GetAccessFlags() & kAccHiddenapiBits; + } +} -// 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 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. +// Returns true if access to `member` should be denied in the given context. +// The decision is based on whether the caller is in a trusted context or not. +// Because determining the access context can be expensive, a lambda function +// "fn_get_access_context" is lazily invoked after other criteria have been +// considered. // 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_is_trusted, - AccessMethod access_method) +inline bool ShouldDenyAccessToMember(T* member, + const std::function<AccessContext()>& fn_get_access_context, + AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(member != nullptr); + const uint32_t runtime_flags = GetRuntimeFlags(member); - // Decode hidden API access flags. - // NB Multiple threads might try to access (and overwrite) these simultaneously, - // causing a race. We only do that if access has not been denied, so the race - // cannot change Java semantics. We should, however, decode the access flags - // once and use it throughout this function, otherwise we may get inconsistent - // results, e.g. print whitelist warnings (b/78327881). - HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags(); - - Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags()); - if (action == kAllow) { - // Nothing to do. - return action; + // Exit early if member is public API. This flag is also set for non-boot class + // path fields/methods. + if ((runtime_flags & kAccPublicApi) != 0) { + return false; } - // 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_is_trusted(self)) { - // Caller is trusted. Exit. - return kAllow; + // Determine which domain the caller and callee belong to. + // This can be *very* expensive. This is why ShouldDenyAccessToMember + // should not be called on every individual access. + const AccessContext caller_context = fn_get_access_context(); + const AccessContext callee_context(member->GetDeclaringClass()); + + // Non-boot classpath callers should have exited early. + DCHECK(!callee_context.IsUntrustedDomain()); + + // Check if the caller is always allowed to access members in the callee context. + if (caller_context.CanAlwaysAccess(callee_context)) { + return false; } - // Member is hidden and caller is not in the platform. - return detail::GetMemberActionImpl(member, api_list, action, access_method); -} + // Check if this is platform accessing core platform. We may warn if `member` is + // not part of core platform API. + switch (caller_context.GetDomain()) { + case Domain::kApplication: { + DCHECK(!callee_context.IsUntrustedDomain()); + + // Exit early if access checks are completely disabled. + EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy(); + if (policy == EnforcementPolicy::kDisabled) { + return false; + } + + // Decode hidden API access flags from the dex file. + // This is an O(N) operation scaling with the number of fields/methods + // in the class. Only do this on slow path and only do it once. + ApiList api_list(detail::GetDexFlags(member)); + DCHECK(api_list.IsValid()); + + // Member is hidden and caller is not exempted. Enter slow path. + return detail::ShouldDenyAccessToMemberImpl(member, api_list, access_method); + } + + case Domain::kPlatform: { + DCHECK(callee_context.GetDomain() == Domain::kCorePlatform); + + // Member is part of core platform API. Accessing it is allowed. + if ((runtime_flags & kAccCorePlatformApi) != 0) { + return false; + } + + // Allow access if access checks are disabled. + EnforcementPolicy policy = Runtime::Current()->GetCorePlatformApiEnforcementPolicy(); + if (policy == EnforcementPolicy::kDisabled) { + return false; + } + + // Access checks are not disabled, report the violation. + // detail::MaybeReportCorePlatformApiViolation(member, caller_context, access_method); + + // Deny access if the policy is enabled. + return policy == EnforcementPolicy::kEnabled; + } -inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller) REQUIRES_SHARED(Locks::mutator_lock_) { - return !caller.IsNull() && - detail::IsCallerTrusted(caller, caller->GetClassLoader(), caller->GetDexCache()); + case Domain::kCorePlatform: { + LOG(FATAL) << "CorePlatform domain should be allowed to access all domains"; + UNREACHABLE(); + } + } } -// Returns true if access to `member` should be denied to a caller loaded with -// `caller_class_loader`. -// This function might print warnings into the log if the member is hidden. +// Helper method for callers where access context can be determined beforehand. +// Wraps AccessContext in a lambda and passes it to the real ShouldDenyAccessToMember. template<typename T> -inline Action GetMemberAction(T* member, - ObjPtr<mirror::ClassLoader> caller_class_loader, - ObjPtr<mirror::DexCache> caller_dex_cache, - AccessMethod access_method) +inline bool ShouldDenyAccessToMember(T* member, + const AccessContext& access_context, + AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { - bool is_caller_trusted = - detail::IsCallerTrusted(/* caller */ nullptr, caller_class_loader, caller_dex_cache); - return GetMemberAction(member, - /* thread */ nullptr, - [is_caller_trusted] (Thread*) { return is_caller_trusted; }, - access_method); + return ShouldDenyAccessToMember(member, [&]() { return access_context; }, access_method); } -// Calls back into managed code to notify VMRuntime.nonSdkApiUsageConsumer that -// |member| was accessed. This is usually called when an API is on the black, -// dark grey or light grey lists. Given that the callback can execute arbitrary -// code, a call to this method can result in thread suspension. -template<typename T> void NotifyHiddenApiListener(T* member) - REQUIRES_SHARED(Locks::mutator_lock_); - - } // namespace hiddenapi } // namespace art |