Android针对非SDK接口的限制(基于Android 10)

8 篇文章 3 订阅
8 篇文章 0 订阅

简介

  从 Android 9(API 级别 28)开始,此平台对应用能使用的非 SDK 接口实施了限制。只要应用引用非 SDK 接口或尝试使用反射或 JNI 来获取其句柄,这些限制就适用。这些限制旨在帮助提升用户体验和开发者体验,为用户降低应用发生崩溃的风险,同时为开发者降低紧急发布的风险。Google官方文档地址:针对非 SDK 接口的限制

全局hiddenapi设置HiddenApiSettings

  AMS有一个HiddenApiSettings类型的成员,负责记录黑名单是否使能(mBlacklistDisabled),hiddenapi豁免名单信息(mExemptionsStr,mExemptions)和hiddenapi限制政策(mPolicy)等信息。HiddenApiSettings通过registerObserver()监听Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS(hiddenapi豁免名单信息)和Settings.Global.HIDDEN_API_POLICY(hiddenapi限制政策)的变化从而调用update()更新内部信息。
  update()函数读取Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS的值,如果是“*”,则所有hiddenapi都获得豁免,黑名单不使能;如果是单一方法(域)签名或者以逗号分隔开来的方法(域)签名,则把它们保存到mExemptions中,通过“–set-api-blacklist-exemptions”加签名的形式传递给zygote,以便在虚拟机中进行设置,例如,通过adb shell settings put global hidden_api_blacklist_exemptions Lorg/apache/xpath/NodeSetDTM;->setItem(II)V可以为该方法加入到hiddenapi豁免名单。然后读取Settings.Global.HIDDEN_API_POLICY的值,将hiddenapi限制政策设置成这个值。
  这个值可以取HIDDEN_API_ENFORCEMENT_DEFAULT,HIDDEN_API_ENFORCEMENT_DISABLED,HIDDEN_API_ENFORCEMENT_JUST_WARN,HIDDEN_API_ENFORCEMENT_ENABLED。
  HIDDEN_API_ENFORCEMENT_DEFAULT:表示是否限制该hiddenapi取决于targetsdk。
  HIDDEN_API_ENFORCEMENT_DISABLED:表示完全禁用hiddenapi限制。
  HIDDEN_API_ENFORCEMENT_JUST_WARN:表示禁用hiddenapi限制但是会打印警告log。
  HIDDEN_API_ENFORCEMENT_ENABLED:表示深灰名单和黑名单里面的hiddenapi会被限制。
  如需允许访问非 SDK 接口,请使用以下 adb 命令:adb shell settings put global hidden_api_policy 1;如需将 API 强制执行策略重置为默认设置,请使用以下命令:adb shell settings delete global hidden_api_policy。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    static class HiddenApiSettings extends ContentObserver
            implements DeviceConfig.OnPropertiesChangedListener {

        private final Context mContext;
        private boolean mBlacklistDisabled;
        private String mExemptionsStr;
        private List<String> mExemptions = Collections.emptyList();
        private int mLogSampleRate = -1;
        private int mStatslogSampleRate = -1;
        @HiddenApiEnforcementPolicy private int mPolicy = HIDDEN_API_ENFORCEMENT_DEFAULT;

        /**
         * Sampling rate for hidden API access event logs with libmetricslogger, as an integer in
         * the range 0 to 0x10000 inclusive.
         *
         * @hide
         */
        public static final String HIDDEN_API_ACCESS_LOG_SAMPLING_RATE =
                "hidden_api_access_log_sampling_rate";

        /**
         * Sampling rate for hidden API access event logging with statslog, as an integer in the
         * range 0 to 0x10000 inclusive.
         *
         * @hide
         */
        public static final String HIDDEN_API_ACCESS_STATSLOG_SAMPLING_RATE =
                "hidden_api_access_statslog_sampling_rate";

        public void onPropertiesChanged(DeviceConfig.Properties properties) {
            int logSampleRate = properties.getInt(HIDDEN_API_ACCESS_LOG_SAMPLING_RATE,
                    mLogSampleRate);
            int statslogSampleRate = properties.getInt(HIDDEN_API_ACCESS_STATSLOG_SAMPLING_RATE,
                    mStatslogSampleRate);
            setSampleRates(logSampleRate, statslogSampleRate);
        }

        private void setSampleRates(int logSampleRate, int statslogSampleRate) {
            if (logSampleRate >= 0 && logSampleRate <= 0x10000
                    && logSampleRate != mLogSampleRate) {
                mLogSampleRate = logSampleRate;
                ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate);
            }

            if (statslogSampleRate >= 0 && statslogSampleRate <= 0x10000
                    && statslogSampleRate != mStatslogSampleRate) {
                mStatslogSampleRate = statslogSampleRate;
                ZYGOTE_PROCESS.setHiddenApiAccessStatslogSampleRate(mStatslogSampleRate);
            }

        }

        /**
         * Set initial sampling rates from DeviceConfig. This is required after each restart,
         * if they never get updated.
         */
        private void initializeSampleRates() {
            int logSampleRate = DeviceConfig.getInt(DeviceConfig.NAMESPACE_APP_COMPAT,
                    HIDDEN_API_ACCESS_LOG_SAMPLING_RATE, 0);
            int statslogSampleRate = DeviceConfig.getInt(DeviceConfig.NAMESPACE_APP_COMPAT,
                    HIDDEN_API_ACCESS_STATSLOG_SAMPLING_RATE, 0);
            setSampleRates(logSampleRate, statslogSampleRate);
        }

        public HiddenApiSettings(Handler handler, Context context) {
            super(handler);
            mContext = context;
        }

        public void registerObserver() {
            mContext.getContentResolver().registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS),
                    false,
                    this);
            mContext.getContentResolver().registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.HIDDEN_API_POLICY),
                    false,
                    this);
            initializeSampleRates();
            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT,
                    mContext.getMainExecutor(), this);
            update();
        }

        private void update() {
            String exemptions = Settings.Global.getString(mContext.getContentResolver(),
                    Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS);
            if (!TextUtils.equals(exemptions, mExemptionsStr)) {
                mExemptionsStr = exemptions;
                if ("*".equals(exemptions)) {
                    mBlacklistDisabled = true;
                    mExemptions = Collections.emptyList();
                } else {
                    mBlacklistDisabled = false;
                    mExemptions = TextUtils.isEmpty(exemptions)
                            ? Collections.emptyList()
                            : Arrays.asList(exemptions.split(","));
                }
                if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) {
                  Slog.e(TAG, "Failed to set API blacklist exemptions!");
                  // leave mExemptionsStr as is, so we don't try to send the same list again.
                  mExemptions = Collections.emptyList();
                }
            }
            mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY);
        }

        private @HiddenApiEnforcementPolicy int getValidEnforcementPolicy(String settingsKey) {
            int policy = Settings.Global.getInt(mContext.getContentResolver(), settingsKey,
                    ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT);
            if (ApplicationInfo.isValidHiddenApiEnforcementPolicy(policy)) {
                return policy;
            } else {
                return ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
            }
        }

        boolean isDisabled() {
            return mBlacklistDisabled;
        }

        @HiddenApiEnforcementPolicy int getPolicy() {
            return mPolicy;
        }

        public void onChange(boolean selfChange) {
            update();
        }
    }

应用进程hiddenapi设置

  在AMS创建进程的过程中,会将携带hiddenapi信息的runtimeflags传递给Zygote从而fork出进程。如果黑名单不使能(即没有通过adb shell settings put global hidden_api_blacklist_exemptions *设置过),则可以单独为该应用进程设置独立的hiddenapi限制政策,保存在ApplicationInfo的mHiddenApiPolicy中,有默认值HIDDEN_API_ENFORCEMENT_DEFAULT。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

    @GuardedBy("mService")
    boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            boolean disableHiddenApiChecks, boolean disableTestApiChecks,
            boolean mountExtStorageFull, String abiOverride) {
            ...
            if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) {
                app.info.maybeUpdateHiddenApiEnforcementPolicy(
                        mService.mHiddenApiBlacklist.getPolicy());
                @ApplicationInfo.HiddenApiEnforcementPolicy int policy =
                        app.info.getHiddenApiEnforcementPolicy();
                int policyBits = (policy << Zygote.API_ENFORCEMENT_POLICY_SHIFT);
                if ((policyBits & Zygote.API_ENFORCEMENT_POLICY_MASK) != policyBits) {
                    throw new IllegalStateException("Invalid API policy: " + policy);
                }
                runtimeFlags |= policyBits;

                if (disableTestApiChecks) {
                    runtimeFlags |= Zygote.DISABLE_TEST_API_ENFORCEMENT_POLICY;
                }
            }
            ...
    }

  maybeUpdateHiddenApiEnforcementPolicy会尝试将HiddenApiSettings里面的全局的hiddenapi限制政策赋给应用进程。getHiddenApiEnforcementPolicy会尝试获取应用进程的hiddenapi限制政策。
  如果在/system/etc/sysconfig/hiddenapi-package-whitelist.xml文件有添加该应用作为hiddenapi白名单的话,则isPackageWhitelistedForHiddenApis()返回true。isAllowedToUseHiddenApis()在1.应用进程为系统签名;2.应用为/system下面或者是经过/system下面应用更新而来的,且配置了android:usesNonSdkApi=true属性;3.应用为/system下面或者是经过/system下面应用更新而来的,且在/system/etc/sysconfig/hiddenapi-package-whitelist.xml文件配置了白名单 满足这三种情况之一返回true。
  换句话说,当isAllowedToUseHiddenApis()返回true是,传给Zygote的是HIDDEN_API_ENFORCEMENT_DISABLED;isAllowedToUseHiddenApis()返回false且全局的hiddenapi限制政策是HIDDEN_API_ENFORCEMENT_DEFAULT时,传给Zygote的是HIDDEN_API_ENFORCEMENT_ENABLED;其他情况传给Zygote的就是全局的hiddenapi限制政策。

/frameworks/base/core/java/android/content/pm/ApplicationInfo.java

    public void maybeUpdateHiddenApiEnforcementPolicy(@HiddenApiEnforcementPolicy int policy) {
        if (isPackageWhitelistedForHiddenApis()) {
            return;
        }
        setHiddenApiEnforcementPolicy(policy);
    }
    ...
        public @HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
        if (isAllowedToUseHiddenApis()) {
            return HIDDEN_API_ENFORCEMENT_DISABLED;
        }
        if (mHiddenApiPolicy != HIDDEN_API_ENFORCEMENT_DEFAULT) {
            return mHiddenApiPolicy;
        }
        return HIDDEN_API_ENFORCEMENT_ENABLED;
    }

设置到虚拟机

  虚拟机在fork出进程后,通过SetHiddenApiEnforcementPolicy设置进程内部虚拟机的hiddenapi限制政策,保存到Runtime的hidden_api_policy_。

art/runtime/native/dalvik_system_ZygoteHooks.cc

static void ZygoteHooks_nativePostForkChild(JNIEnv* env,
                                            jclass,
                                            jlong token,
                                            jint runtime_flags,
                                            jboolean is_system_server,
                                            jboolean is_zygote,
                                            jstring instruction_set) {
                                            ...
  hiddenapi::EnforcementPolicy api_enforcement_policy = hiddenapi::EnforcementPolicy::kDisabled;
  ...
  api_enforcement_policy = hiddenapi::EnforcementPolicyFromInt(
      (runtime_flags & HIDDEN_API_ENFORCEMENT_POLICY_MASK) >> API_ENFORCEMENT_POLICY_SHIFT);
  runtime_flags &= ~HIDDEN_API_ENFORCEMENT_POLICY_MASK;

  if ((runtime_flags & DISABLE_TEST_API_ENFORCEMENT_POLICY) != 0u) {
    runtime->SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kDisabled);
  } else {
    runtime->SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
  }
  runtime_flags &= ~DISABLE_TEST_API_ENFORCEMENT_POLICY;
  ...
    bool do_hidden_api_checks = api_enforcement_policy != hiddenapi::EnforcementPolicy::kDisabled;
  DCHECK(!(is_system_server && do_hidden_api_checks))
      << "SystemServer should be forked with EnforcementPolicy::kDisable";
  DCHECK(!(is_zygote && do_hidden_api_checks))
      << "Child zygote processes should be forked with EnforcementPolicy::kDisable";
  runtime->SetHiddenApiEnforcementPolicy(api_enforcement_policy);
  runtime->SetDedupeHiddenApiWarnings(true);
  if (api_enforcement_policy != hiddenapi::EnforcementPolicy::kDisabled &&
      runtime->GetHiddenApiEventLogSampleRate() != 0) {
    // Hidden API checks are enabled, and we are sampling access for the event log. Initialize the
    // random seed, to ensure the sampling is actually random. We do this post-fork, as doing it
    // pre-fork would result in the same sequence for every forked process.
    std::srand(static_cast<uint32_t>(NanoTime()));
  }
  ...

  而hiddenapi豁免名单信息则通过SetHiddenApiExemptions设置到Runtime的hidden_api_exemptions_。

art/runtime/native/dalvik_system_VMRuntime.cc

static void VMRuntime_setHiddenApiExemptions(JNIEnv* env,
                                            jclass,
                                            jobjectArray exemptions) {
  std::vector<std::string> exemptions_vec;
  int exemptions_length = env->GetArrayLength(exemptions);
  for (int i = 0; i < exemptions_length; i++) {
    jstring exemption = reinterpret_cast<jstring>(env->GetObjectArrayElement(exemptions, i));
    const char* raw_exemption = env->GetStringUTFChars(exemption, nullptr);
    exemptions_vec.push_back(raw_exemption);
    env->ReleaseStringUTFChars(exemption, raw_exemption);
  }

  Runtime::Current()->SetHiddenApiExemptions(exemptions_vec);
}

反射调用

  反射获取成员方法常用的一个方法是getDeclaredMethod。

libcore/ojluni/src/main/java/java/lang/Class.java

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        return getMethod(name, parameterTypes, false);
    }

    private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods)
            throws NoSuchMethodException {
        if (name == null) {
            throw new NullPointerException("name == null");
        }
        if (parameterTypes == null) {
            parameterTypes = EmptyArray.CLASS;
        }
        for (Class<?> c : parameterTypes) {
            if (c == null) {
                throw new NoSuchMethodException("parameter type is null");
            }
        }
        Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes)
                                               : getDeclaredMethodInternal(name, parameterTypes);
        // Fail if we didn't find the method or it was expected to be public.
        if (result == null ||
            (recursivePublicMethods && !Modifier.isPublic(result.getAccessFlags()))) {
            throw new NoSuchMethodException(getName() + "." + name + " "
                    + Arrays.toString(parameterTypes));
        }
        return result;
    }

  最终调用到Class_getDeclaredMethodInternal:

art/runtime/native/java_lang_Class.cc

static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                               jstring name, jobjectArray args) {
  ScopedFastNativeObjectAccess soa(env);
  StackHandleScope<1> hs(soa.Self());
  DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
  DCHECK(!Runtime::Current()->IsActiveTransaction());
  ObjPtr<mirror::Class> klass = DecodeClass(soa, javaThis);
  if (UNLIKELY(klass->IsObsoleteObject())) {
    ThrowRuntimeException("Obsolete Object!");
    return nullptr;
  }
  Handle<mirror::Method> result = hs.NewHandle(
      mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
          soa.Self(),
          klass,
          soa.Decode<mirror::String>(name),
          soa.Decode<mirror::ObjectArray<mirror::Class>>(args),
          GetHiddenapiAccessContextFunction(soa.Self())));
  if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
    return nullptr;
  }
  return soa.AddLocalReference<jobject>(result.Get());
}

  Class::GetDeclaredMethodInternal的内部实现。简单地说,就是先遍历类中的虚方法集合(public和protected方法集合),找到类名和参数匹配的方法;如果找不到,再遍历类中的直接方法集合(static, private, init方法集合),找到类名和参数匹配的方法。

art/runtime/mirror/class.cc

template <PointerSize kPointerSize, bool kTransactionActive>
ObjPtr<Method> Class::GetDeclaredMethodInternal(
    Thread* self,
    ObjPtr<Class> klass,
    ObjPtr<String> name,
    ObjPtr<ObjectArray<Class>> args,
    const std::function<hiddenapi::AccessContext()>& fn_get_access_context) {
  // Covariant return types (or smali) permit the class to define
  // multiple methods with the same name and parameter types.
  // Prefer (in decreasing order of importance):
  //  1) non-hidden method over hidden
  //  2) virtual methods over direct
  //  3) non-synthetic methods over synthetic
  // We never return miranda methods that were synthesized by the runtime.
  StackHandleScope<3> hs(self);
  auto h_method_name = hs.NewHandle(name);
  if (UNLIKELY(h_method_name == nullptr)) {
    ThrowNullPointerException("name == null");
    return nullptr;
  }
  auto h_args = hs.NewHandle(args);
  Handle<Class> h_klass = hs.NewHandle(klass);
  constexpr hiddenapi::AccessMethod access_method = hiddenapi::AccessMethod::kNone;
  ArtMethod* result = nullptr;
  bool result_hidden = false;
  for (auto& m : h_klass->GetDeclaredVirtualMethods(kPointerSize)) {
    if (m.IsMiranda()) {
      continue;
    }
    auto* np_method = m.GetInterfaceMethodIfProxy(kPointerSize);
    // May cause thread suspension.
    ObjPtr<String> np_name = np_method->ResolveNameString();
    if (!np_name->Equals(h_method_name.Get()) || !np_method->EqualParameters(h_args)) {
      if (UNLIKELY(self->IsExceptionPending())) {
        return nullptr;
      }
      continue;
    }
    bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
    if (!m_hidden && !m.IsSynthetic()) {
      // Non-hidden, virtual, non-synthetic. Best possible result, exit early.
      return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
    } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
      // Remember as potential result.
      result = &m;
      result_hidden = m_hidden;
    }
  }

  if ((result != nullptr) && !result_hidden) {
    // We have not found a non-hidden, virtual, non-synthetic method, but
    // if we have found a non-hidden, virtual, synthetic method, we cannot
    // do better than that later.
    DCHECK(!result->IsDirect());
    DCHECK(result->IsSynthetic());
  } else {
    for (auto& m : h_klass->GetDirectMethods(kPointerSize)) {
      auto modifiers = m.GetAccessFlags();
      if ((modifiers & kAccConstructor) != 0) {
        continue;
      }
      auto* np_method = m.GetInterfaceMethodIfProxy(kPointerSize);
      // May cause thread suspension.
      ObjPtr<String> np_name = np_method->ResolveNameString();
      if (np_name == nullptr) {
        self->AssertPendingException();
        return nullptr;
      }
      if (!np_name->Equals(h_method_name.Get()) || !np_method->EqualParameters(h_args)) {
        if (UNLIKELY(self->IsExceptionPending())) {
          return nullptr;
        }
        continue;
      }
      DCHECK(!m.IsMiranda());  // Direct methods cannot be miranda methods.
      bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
      if (!m_hidden && !m.IsSynthetic()) {
        // Non-hidden, direct, non-synthetic. Any virtual result could only have been
        // hidden, therefore this is the best possible match. Exit now.
        DCHECK((result == nullptr) || result_hidden);
        return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
      } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
        // Remember as potential result.
        result = &m;
        result_hidden = m_hidden;
      }
    }
  }

  return result != nullptr
      ? Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, result)
      : nullptr;
}

  找到名字和参数匹配的方法后,也不是直接返回结果的,还需要通过ShouldDenyAccessToMember函数确认这个方法是否可以被调用者访问。如果可以直接访问,直接通过CreateFromArtMethod返回一个Method;另外,还是用了result变量来保存当前获得的最佳结果,每次找到名字和参数匹配,但是没有权限访问的方法后,会通过IsMethodPreferredOver方法来和之前的最佳结果进行比较,如果新的方法更佳,会将result设置为新的方法。也就是说,Class.cc的内部方法GetDeclaredMethodInternal在能够寻找到签名匹配的方法的前提下,总会返回一个方法。
  IsMethodPreferredOver方法的对比原则是:有权限访问的方法由于没有权限访问的方法;直接方法优于虚方法;非合成方法优于合成方法。

art/runtime/mirror/class.cc

ALWAYS_INLINE
static bool IsMethodPreferredOver(ArtMethod* orig_method,
                                  bool orig_method_hidden,
                                  ArtMethod* new_method,
                                  bool new_method_hidden) {
  DCHECK(new_method != nullptr);

  // Is this the first result?
  if (orig_method == nullptr) {
    return true;
  }

  // Original method is hidden, the new one is not?
  if (orig_method_hidden && !new_method_hidden) {
    return true;
  }

  // We iterate over virtual methods first and then over direct ones,
  // so we can never be in situation where `orig_method` is direct and
  // `new_method` is virtual.
  DCHECK(!orig_method->IsDirect() || new_method->IsDirect());

  // Original method is synthetic, the new one is not?
  if (orig_method->IsSynthetic() && !new_method->IsSynthetic()) {
    return true;
  }

  return false;
}

  caller_context表示调用者的准入域,callee_context表示被调用者的准入域。虚拟机内部将准入域分成3类:kCorePlatform,kPlatform和kApplication。其中可信程度:kCorePlatform>kPlatform>kApplication。可信程度高的调用者总是可以访问可信程度相对低的被调用者(参考CanAlwaysAccess方法)。
  如果可信程度低的调用者访问可信程度高的调用者,则按调用者的准入域来确定是否可以访问。kApplication准入域的访问权限由ShouldDenyAccessToMemberImpl决定,kPlatform准入域的访问权限由HandleCorePlatformApiViolation,kCorePlatform对任意准入域的成员都有访问权限。

art/runtime/mirror/class.cc

template<typename T>
inline bool ShouldDenyAccessToMember(T* member,
                                     const std::function<AccessContext()>& fn_get_access_context,
                                     AccessMethod access_method)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  DCHECK(member != nullptr);

  // 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::Current()->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();
    }
  }
}

  先看看决定kApplication准入域准入权限的函数ShouldDenyAccessToMemberImpl。ShouldDenyAccessToMemberImpl接收三个参数:member是要访问的方法或者域;api_list是方法或者域的的受限制情况,指明该方法或者域是否在白名单之列,如果不在白名单之列,在哪个版本的SDK可以开放等信息;access_method指的是访问该方法或者域的方式,主要分为:
  1.kNone(虚拟机内部使用,上面GetDeclaredMethodInternal用的是此方式)
  2.kReflection(反射调用使用)
  3.kJNI = 2(JNI调用使用)
  4. kLinking = 3(类链接使用)

  访问政策如下:
  1.如果方法或域签名在hiddenapi豁免的白名单内,例如通过adb shell settings put global hidden_api_blacklist_exemptions Lorg/apache/xpath/NodeSetDTM;->setItem(II)V的方式设置,是可以访问的;
  2.对于testapi(带@test注解),在hiddenapi的限制政策开启的情况下,如果testapi限制政策禁用哪个,则可以访问这个testapi;
  3.应用进程在创建虚拟机副本时,会根据自身的targetsdk传入参数到虚拟机作为虚拟机的targetsdk。在hiddenapi的限制政策开启的情况下,当虚拟机的targetsdk大于hiddenapi指定的api时,访问会被拒绝;小于等于hiddenapi指定的api时,访问被允许。
  4.在hiddenapi的限制政策禁用的情况下,访问总是可以被允许。

  代码注解以及其含义:

注释含义
@UnsupportedAppUsage不受限制的灰名单
@UnsupportedAppUsage(maxTargetSdk = 0)黑名单
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)受限制的灰名单。仅供以 Android 8.1 Oreo(API 级别 27)或更低版本为目标平台的应用进行访问。
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)受限制的灰名单。仅供以 Android 9 Pie(API 级别 28)或更低版本为目标平台的应用进行访问。

art/runtime/hidden_api.cc

template<typename T>
bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
  DCHECK(member != nullptr);
  Runtime* runtime = Runtime::Current();

  EnforcementPolicy hiddenApiPolicy = runtime->GetHiddenApiEnforcementPolicy();
  DCHECK(hiddenApiPolicy != EnforcementPolicy::kDisabled)
      << "Should never enter this function when access checks are completely disabled";

  MemberSignature member_signature(member);

  // 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.
    MaybeUpdateAccessFlags(runtime, member, kAccPublicApi);
    return false;
  }

  EnforcementPolicy testApiPolicy = runtime->GetTestApiEnforcementPolicy();

  bool deny_access = false;
  if (hiddenApiPolicy == EnforcementPolicy::kEnabled) {
    if (testApiPolicy == EnforcementPolicy::kDisabled && api_list.IsTestApi()) {
      deny_access = false;
    } else {
      deny_access = IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(),
                                               api_list.GetMaxAllowedSdkVersion());
    }
  }

  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, deny_access);
    }

    // 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) {
        const uint32_t sampled_value = static_cast<uint32_t>(std::rand()) & 0xffff;
        if (sampled_value < eventLogSampleRate) {
          member_signature.LogAccessToEventLog(sampled_value, access_method, deny_access);
        }
      }
    }

    // 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) {
      MaybeUpdateAccessFlags(runtime, member, kAccPublicApi);
    }
  }

  return deny_access;
}

  在java_lang_Class.cc的Class_getDeclaredMethodInternal方法中还会调用一次ShouldDenyAccessToMember,如果权限被拒绝,这次会打印出具体的警告log例如
  Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI),并且令Class_getDeclaredMethodInternal返回null,导致上层的Class#getDeclaredMethod调用抛出NoSuchMethodException异常。

  java_lang_Class.cc里面的ShouldDenyAccessToMember和class.cc里面GetDeclaredMethodInternal的ShouldDenyAccessToMember不同,java_lang_Class.cc是经Java层的调用,访问方式会被设置成kReflection(反射),class.cc是虚拟机内部类的实现,访问方式会被设置成kNone。

art/runtime/native/java_lang_Class.cc

// Returns true if the first non-ClassClass caller up the stack should not be
// allowed access to `member`.
template<typename T>
ALWAYS_INLINE static bool ShouldDenyAccessToMember(T* member, Thread* self)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  return hiddenapi::ShouldDenyAccessToMember(member,
                                             GetHiddenapiAccessContextFunction(self),
                                             hiddenapi::AccessMethod::kReflection);
}

  在访问方式不等于kNone的情况下,还会有以下这些额外步骤:
  在访问权限被拒绝(deny_access为true)或者调用者为debuggable app(runtime->IsJavaDebuggable为true)的情况下,会打印出权限拒绝的log,例如:
  Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)。

  在访问权限被授予的情况下,虚拟机内部还会为这个方法添加kAccPublicApi的flag,以方便下次再次调用ShouldDenyAccessToMember可以尽早以false的结果返回。

art/runtime/hidden_api.cc

template<typename T>
bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
  ...
  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, deny_access);
    }

    // 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) {
        const uint32_t sampled_value = static_cast<uint32_t>(std::rand()) & 0xffff;
        if (sampled_value < eventLogSampleRate) {
          member_signature.LogAccessToEventLog(sampled_value, access_method, deny_access);
        }
      }
    }

JNI调用

  JNI调用的情况跟反射的大同小异。入口在jni_internal.cc,也会调用ShouldDenyAccessToMember决定访问权限:

art/runtime/jni/jni_internal.cc

ArtMethod* FindMethodJNI(const ScopedObjectAccess& soa,
                         jclass jni_class,
                         const char* name,
                         const char* sig,
                         bool is_static) {
  ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));
  if (c == nullptr) {
    return nullptr;
  }
  ArtMethod* method = nullptr;
  auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
  if (c->IsInterface()) {
    method = c->FindInterfaceMethod(name, sig, pointer_size);
  } else {
    method = c->FindClassMethod(name, sig, pointer_size);
  }
  if (method != nullptr && ShouldDenyAccessToMember(method, soa.Self())) {
    method = nullptr;
  }
  if (method == nullptr || method->IsStatic() != is_static) {
    ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
    return nullptr;
  }
  return method;
}

  虚拟机内部查找方法的过程如下:1.现在当前类的虚方法和直接方法中查找;2.遍历当前类的所有父类,在这些父类的虚方法和直接方法中查找;3.遍历当前类的所有父类,在这些父类的拷贝方法中查找。如果有返回结果,再在jni_internal.cc中通过ShouldDenyAccessToMember方法进行hiddenapi限制检验确认最终结果。

art/runtime/mirror/class.cc


template <typename SignatureType>
static inline ArtMethod* FindClassMethodWithSignature(ObjPtr<Class> this_klass,
                                                      std::string_view name,
                                                      const SignatureType& signature,
                                                      PointerSize pointer_size)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  // Search declared methods first.
  for (ArtMethod& method : this_klass->GetDeclaredMethodsSlice(pointer_size)) {
    ArtMethod* np_method = method.GetInterfaceMethodIfProxy(pointer_size);
    if (np_method->GetName() == name && np_method->GetSignature() == signature) {
      return &method;
    }
  }

  // Then search the superclass chain. If we find an inherited method, return it.
  // If we find a method that's not inherited because of access restrictions,
  // try to find a method inherited from an interface in copied methods.
  ObjPtr<Class> klass = this_klass->GetSuperClass();
  ArtMethod* uninherited_method = nullptr;
  for (; klass != nullptr; klass = klass->GetSuperClass()) {
    DCHECK(!klass->IsProxyClass());
    for (ArtMethod& method : klass->GetDeclaredMethodsSlice(pointer_size)) {
      if (method.GetName() == name && method.GetSignature() == signature) {
        if (IsInheritedMethod(this_klass, klass, method)) {
          return &method;
        }
        uninherited_method = &method;
        break;
      }
    }
    if (uninherited_method != nullptr) {
      break;
    }
  }

  // Then search copied methods.
  // If we found a method that's not inherited, stop the search in its declaring class.
  ObjPtr<Class> end_klass = klass;
  DCHECK_EQ(uninherited_method != nullptr, end_klass != nullptr);
  klass = this_klass;
  if (UNLIKELY(klass->IsProxyClass())) {
    DCHECK(klass->GetCopiedMethodsSlice(pointer_size).empty());
    klass = klass->GetSuperClass();
  }
  for (; klass != end_klass; klass = klass->GetSuperClass()) {
    DCHECK(!klass->IsProxyClass());
    for (ArtMethod& method : klass->GetCopiedMethodsSlice(pointer_size)) {
      if (method.GetName() == name && method.GetSignature() == signature) {
        return &method;  // No further check needed, copied methods are inherited by definition.
      }
    }
  }
  return uninherited_method;  // Return the `uninherited_method` if any.
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Invoker123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值