/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ART_RUNTIME_HIDDEN_API_H_ #define ART_RUNTIME_HIDDEN_API_H_ #include "art_field.h" #include "art_method.h" #include "base/hiddenapi_domain.h" #include "base/hiddenapi_flags.h" #include "base/locks.h" #include "intrinsics_enum.h" #include "jni/jni_internal.h" #include "mirror/class-inl.h" #include "reflection.h" #include "runtime.h" #include "well_known_classes.h" namespace art { namespace hiddenapi { // Hidden API enforcement policy // This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in // frameworks/base/core/java/android/content/pm/ApplicationInfo.java enum class EnforcementPolicy { kDisabled = 0, kJustWarn = 1, // keep checks enabled, but allow everything (enables logging) kEnabled = 2, // ban conditionally blocked & blocklist kMax = kEnabled, }; inline EnforcementPolicy EnforcementPolicyFromInt(int api_policy_int) { DCHECK_GE(api_policy_int, 0); DCHECK_LE(api_policy_int, static_cast(EnforcementPolicy::kMax)); return static_cast(api_policy_int); } // 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, }; // 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 class_loader, ObjPtr dex_cache) REQUIRES_SHARED(Locks::mutator_lock_) : klass_(nullptr), dex_file_(GetDexFileFromDexCache(dex_cache)), domain_(ComputeDomain(class_loader, dex_file_)) {} // Initialize from class loader and dex file (only used by tests). AccessContext(ObjPtr class_loader, const DexFile* dex_file) : klass_(nullptr), dex_file_(dex_file), domain_(ComputeDomain(class_loader, dex_file_)) {} // Initialize from Class. explicit AccessContext(ObjPtr klass) REQUIRES_SHARED(Locks::mutator_lock_) : klass_(klass), dex_file_(GetDexFileFromDexCache(klass->GetDexCache())), domain_(ComputeDomain(klass, dex_file_)) {} ObjPtr GetClass() const { return klass_; } const DexFile* GetDexFile() const { return dex_file_; } Domain GetDomain() const { return domain_; } bool IsApplicationDomain() 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_); } private: static const DexFile* GetDexFileFromDexCache(ObjPtr dex_cache) REQUIRES_SHARED(Locks::mutator_lock_) { return dex_cache.IsNull() ? nullptr : dex_cache->GetDexFile(); } static Domain ComputeDomain(bool is_trusted) { return is_trusted ? Domain::kCorePlatform : Domain::kApplication; } static Domain ComputeDomain(ObjPtr class_loader, const DexFile* dex_file) { if (dex_file == nullptr) { return ComputeDomain(/* is_trusted= */ class_loader.IsNull()); } return dex_file->GetHiddenapiDomain(); } static Domain ComputeDomain(ObjPtr 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 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: explicit ScopedHiddenApiEnforcementPolicySetting(EnforcementPolicy new_policy) : initial_policy_(Runtime::Current()->GetHiddenApiEnforcementPolicy()) { Runtime::Current()->SetHiddenApiEnforcementPolicy(new_policy); } ~ScopedHiddenApiEnforcementPolicySetting() { Runtime::Current()->SetHiddenApiEnforcementPolicy(initial_policy_); } private: const EnforcementPolicy initial_policy_; DISALLOW_COPY_AND_ASSIGN(ScopedHiddenApiEnforcementPolicySetting); }; void InitializeCorePlatformApiPrivateFields() REQUIRES(!Locks::mutator_lock_); // Implementation details. DO NOT ACCESS DIRECTLY. namespace detail { // Class to encapsulate the signature of a member (ArtField or ArtMethod). This // is used as a helper when matching prefixes, and when logging the signature. class MemberSignature { private: enum MemberType { kField, kMethod, }; std::string class_name_; std::string member_name_; std::string type_signature_; std::string tmp_; MemberType type_; inline std::vector GetSignatureParts() const; 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) bool DoesPrefixMatch(const std::string& prefix) const; bool DoesPrefixMatchAny(const std::vector& exemptions); void WarnAboutAccess(AccessMethod access_method, ApiList list, bool access_denied); void LogAccessToEventLog(uint32_t sampled_value, AccessMethod access_method, bool access_denied); // Calls back into managed code to notify VMRuntime.nonSdkApiUsageConsumer that // |member| was accessed. This is usually called when an API is unsupported, // conditionally or unconditionally blocked. 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 uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_); // Handler of detected core platform API violations. Returns true if access to // `member` should be denied. template bool HandleCorePlatformApiViolation(T* member, const AccessContext& caller_context, AccessMethod access_method, EnforcementPolicy policy) REQUIRES_SHARED(Locks::mutator_lock_); template bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_); inline ArtField* GetInterfaceMemberIfProxy(ArtField* field) { return field; } inline ArtMethod* GetInterfaceMemberIfProxy(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { return method->GetInterfaceMethodIfProxy(kRuntimePointerSize); } // Returns access flags for the runtime representation of a class member (ArtField/ArtMember). ALWAYS_INLINE inline uint32_t CreateRuntimeFlags_Impl(uint32_t dex_flags) { uint32_t runtime_flags = 0u; ApiList api_list(dex_flags); DCHECK(api_list.IsValid()); if (api_list.Contains(ApiList::Sdk())) { 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; } } DCHECK_EQ(runtime_flags & kAccHiddenapiBits, runtime_flags) << "Runtime flags not in reserved access flags bits"; return runtime_flags; } } // namespace detail // Returns access flags for the runtime representation of a class member (ArtField/ArtMember). ALWAYS_INLINE inline uint32_t CreateRuntimeFlags(const ClassAccessor::BaseItem& member) { return detail::CreateRuntimeFlags_Impl(member.GetHiddenapiFlags()); } // Returns access flags for the runtime representation of a class member (ArtField/ArtMember). template ALWAYS_INLINE inline uint32_t CreateRuntimeFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_) { return detail::CreateRuntimeFlags_Impl(detail::GetDexFlags(member)); } // 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; } // 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(method->GetIntrinsic())) { case Intrinsics::kSystemArrayCopyChar: case Intrinsics::kStringGetCharsNoCheck: case Intrinsics::kReferenceGetReferent: case Intrinsics::kReferenceRefersTo: case Intrinsics::kMemoryPeekByte: case Intrinsics::kMemoryPokeByte: 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::kUnsafeCASInt: case Intrinsics::kUnsafeCASLong: case Intrinsics::kUnsafeCASObject: case Intrinsics::kUnsafeGetAndAddInt: case Intrinsics::kUnsafeGetAndAddLong: case Intrinsics::kUnsafeGetAndSetInt: case Intrinsics::kUnsafeGetAndSetLong: case Intrinsics::kUnsafeGetAndSetObject: case Intrinsics::kUnsafeGetLongVolatile: case Intrinsics::kUnsafeGetObjectVolatile: case Intrinsics::kUnsafeGetVolatile: case Intrinsics::kUnsafePutLongOrdered: case Intrinsics::kUnsafePutLongVolatile: case Intrinsics::kUnsafePutObjectOrdered: case Intrinsics::kUnsafePutObjectVolatile: case Intrinsics::kUnsafePutOrdered: case Intrinsics::kUnsafePutVolatile: case Intrinsics::kUnsafeLoadFence: case Intrinsics::kUnsafeStoreFence: case Intrinsics::kUnsafeFullFence: 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; case Intrinsics::kFP16Ceil: case Intrinsics::kFP16Floor: case Intrinsics::kFP16Greater: case Intrinsics::kFP16GreaterEquals: case Intrinsics::kFP16Less: case Intrinsics::kFP16LessEquals: case Intrinsics::kFP16ToFloat: case Intrinsics::kFP16ToHalf: case Intrinsics::kFP16Rint: case Intrinsics::kUnsafeGet: case Intrinsics::kUnsafeGetLong: case Intrinsics::kUnsafeGetObject: case Intrinsics::kUnsafePutLong: case Intrinsics::kUnsafePut: case Intrinsics::kUnsafePutObject: return kAccCorePlatformApi; default: // Remaining intrinsics are public API. We DCHECK that in SetIntrinsic(). return kAccPublicApi; } } else { return method->GetAccessFlags() & kAccHiddenapiBits; } } // Called by class linker when a new dex file has been registered. Assigns // the AccessContext domain to the newly-registered dex file based on its // location and class loader. void InitializeDexFileDomain(const DexFile& dex_file, ObjPtr class_loader); // 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 inline bool ShouldDenyAccessToMember(T* member, const std::function& fn_get_access_context, AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(member != nullptr); // First check if we have an explicit sdk checker installed that should be used to // verify access. If so, make the decision based on it. // // This is used during off-device AOT compilation which may want to generate verification // metadata only for a specific list of public SDKs. Note that the check here is made // based on descriptor equality and it's aim to further restrict a symbol that would // otherwise be resolved. // // The check only applies to boot classpaths dex files. Runtime* runtime = Runtime::Current(); if (UNLIKELY(runtime->IsAotCompiler())) { if (member->GetDeclaringClass()->GetClassLoader() == nullptr && runtime->GetClassLinker()->DenyAccessBasedOnPublicSdk(member)) { return true; } } // Get the runtime flags encoded in member's access flags. // Note: this works for proxy methods because they inherit access flags from their // respective interface methods. const uint32_t runtime_flags = GetRuntimeFlags(member); // 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; } // 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.IsApplicationDomain()); // Check if the caller is always allowed to access members in the callee context. if (caller_context.CanAlwaysAccess(callee_context)) { return false; } // 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.IsApplicationDomain()); // Exit early if access checks are completely disabled. EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy(); if (policy == EnforcementPolicy::kDisabled) { return false; } // If this is a proxy method, look at the interface method instead. member = detail::GetInterfaceMemberIfProxy(member); // 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; } // If this is a proxy method, look at the interface method instead. member = detail::GetInterfaceMemberIfProxy(member); // Access checks are not disabled, report the violation. // This may also add kAccCorePlatformApi to the access flags of `member` // so as to not warn again on next access. return detail::HandleCorePlatformApiViolation(member, caller_context, access_method, policy); } case Domain::kCorePlatform: { LOG(FATAL) << "CorePlatform domain should be allowed to access all domains"; UNREACHABLE(); } } } // Helper method for callers where access context can be determined beforehand. // Wraps AccessContext in a lambda and passes it to the real ShouldDenyAccessToMember. template inline bool ShouldDenyAccessToMember(T* member, const AccessContext& access_context, AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { return ShouldDenyAccessToMember(member, [&]() { return access_context; }, access_method); } } // namespace hiddenapi } // namespace art #endif // ART_RUNTIME_HIDDEN_API_H_