summaryrefslogtreecommitdiff
path: root/runtime/hidden_api.h
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/hidden_api.h')
-rw-r--r--runtime/hidden_api.h445
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