Android P @hide API Alert

原文地址:

https://www.fatalerrors.org/a/detected-problems-with-api-compatibility.html

Detected problems with API compatibility(visit g.co/dev/appcompat for more info)

Recently, the mobile phone has upgraded Android 9. When entering the application, it will pop up a box

Detected problems with API compatibility(visit g.co/dev/appcompat for more info)

I was scared to sweat. I looked at the information on the corresponding website. It turned out that android restricted the use of the hide annotation api. Note that this is not the original hide annotation API in the sdk, but the virtual machine level.
This article is used to record the entire investigation process.

First, the location of the warning pop-up is located in the performRestart function of Activity.java.

7238         // This property is set for all non-user builds except final release
7239         boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1;
7240 
7241         if (isAppDebuggable || isApiWarningEnabled) {
7242             if (!mMainThread.mHiddenApiWarningShown && VMRuntime.getRuntime().hasUsedHiddenApi()) {
7243                 // Only show the warning once per process.
7244                 mMainThread.mHiddenApiWarningShown = true;
7245 
7246                 String appName = getApplicationInfo().loadLabel(getPackageManager())
7247                         .toString();
7248                 String warning = "Detected problems with API compatibility\n"
7249                                  + "(visit g.co/dev/appcompat for more info)";
7250                 if (isAppDebuggable) {
7251                     new AlertDialog.Builder(this)
7252                         .setTitle(appName)
7253                         .setMessage(warning)
7254                         .setPositiveButton(android.R.string.ok, null)
7255                         .setCancelable(false)
7256                         .show();
7257                 } else {
7258                     Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
7259                 }
7260             }
7261         }
  1. Line 7241 checks to see if the user has invoked the hidden API (@hide annotated api) if the application has debug mode on, or the ro.art.hiddenapi.warning property is 1.
  2. After the previous step, 7241 is checked, provided that the application starts without a warning to invoke the hidden api, and during this time the hidden API is invoked, then the warning pops up.
  3. There are two ways to warn. The first is that the tunable application uses a dialog pop-up warning, or else uses the toast pop-up warning.

After the above analysis, we know the general process.
VMRuntime.getRuntime().hasUsedHiddenApi() is the basis for judging whether an application has called a hidden function.

VMRuntime is the Runtime class in the runtime.cc of the art code when the art virtual machine is running. We know that the call between java and C ++ uses jni as the binder. The corresponding jni code is in art/runtime/native/dalvik_system_VMRuntime.cc, and the function is

static jboolean VMRuntime_hasUsedHiddenApi(JNIEnv*, jobject) {
  return Runtime::Current()->HasPendingHiddenApiWarning() ? JNI_TRUE : JNI_FALSE;
}

art Runtime is a singleton. We analyze the HasPendingHiddenApiWarning function.

  bool HasPendingHiddenApiWarning() const {
    return pending_hidden_api_warning_;
  }

That is to read the variable value of pending_hidden_api_warning_.

So what we need to focus on is where the value is set.

void SetPendingHiddenApiWarning(bool value) {
    pending_hidden_api_warning_ = value;
  }

There are three places to call the function, two of which are set to false to indicate that we don't need to care about clearing the variable, so there is only one place in the art/runtime/hidden_api.cc file.
The code we need to care about is as follows.

template<typename T>
209 Action GetMemberActionImpl(T* member,
210                            HiddenApiAccessFlags::ApiList api_list,
211                            Action action,
212                            AccessMethod access_method)

This is a template function. There are two implementations.

// Need to instantiate this.
template Action GetMemberActionImpl<ArtField>(ArtField* member,
                                              HiddenApiAccessFlags::ApiList api_list,
                                              Action action,
                                              AccessMethod access_method);
template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member,
                                               HiddenApiAccessFlags::ApiList api_list,
                                               Action action,
                                               AccessMethod access_method);

Before we analyze the GetMemberActionImpl function, let's figure out where to call it, and look through all the code we found in the art/runtime/hidden_api.h file

template<typename T>
inline Action GetMemberAction(T* member,
                              Thread* self,
                              std::function<bool(Thread*)> fn_caller_is_trusted,
                              AccessMethod access_method)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  DCHECK(member != nullptr);

  // 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;
  }

  // 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;
  }

  // Member is hidden and caller is not in the platform.
  return detail::GetMemberActionImpl(member, api_list, action, access_method);
}

Two parameters api_list and action are validated in the function. The api_list parameter is obtained using the member->GetHiddenApiAccessFlags() function, which is actually an enumeration type, and the code uses the function or variable type, including the following
enum ApiList {
kWhitelist = 0, whitelist function
kLightGreylist, white grey list
kDarkGreylist, grey list
kBlacklist, blacklist
kNoList, not in the list.
};
Action represents the default action performed by different list listings, and is also an enumeration variable.
GetActionFromAccessFlags(member->GetHiddenApiAccessFlags())

enum Action {
  kAllow,  //pass
  kAllowButWarn,  //Pass but warn
  kAllowButWarnAndToast,  //Pass but warn by toast
  kDeny  //refuse
};
inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) {
  if (api_list == HiddenApiAccessFlags::kWhitelist) {
    return kAllow;  //The default action of white list is through.
  }

//Next, we need to decide how to execute the default action based on EnforcementPolicy.
  EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
  if (policy == EnforcementPolicy::kNoChecks) {
    // Exit early. Nothing to enforce.
    return kAllow;
  }

  // if policy is "just warn", always warn. We returned above for whitelist APIs.
  if (policy == EnforcementPolicy::kJustWarn) {
    return kAllowButWarn;
  }
  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;
  }
}

After reading the meaning of app_list and action, we return to analyze the GetMemberActionImpl function.

208 template<typename T>
209 Action GetMemberActionImpl(T* member,
210                            HiddenApiAccessFlags::ApiList api_list,
211                            Action action,
212                            AccessMethod access_method) {
213   DCHECK_NE(action, kAllow);
214 
215   // Get the signature, we need it later.
216   MemberSignature member_signature(member);
217 
218   Runtime* runtime = Runtime::Current();
219 
220   // Check for an exemption first. Exempted APIs are treated as white list.
221   // We only do this if we're about to deny, or if the app is debuggable. This is because:
222   // - we only print a warning for light greylist violations for debuggable apps
223   // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs.
224   // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever
225   //   possible.
226   const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
227   if (shouldWarn || action == kDeny) {  
228     if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
              //1 for the function in the exempted list, pass directly.
229       action = kAllow;
230       // Avoid re-examining the exemption list next time.
231       // Note this results in no warning for the member, which seems like what one would expect.
232       // Exemptions effectively adds new members to the whitelist.
233       MaybeWhitelistMember(runtime, member);    //Add to white list
234       return kAllow;
235     }
236 
237     if (access_method != kNone) {
238       // Print a log message with information about this class member access.
239       // We do this if we're about to block access, or the app is debuggable.
240       member_signature.WarnAboutAccess(access_method, api_list); //2 print log that cannot be passed directly.
241     }
242   }
243 
244   if (kIsTargetBuild && !kIsTargetLinux) {
245     uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
246     // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
247     static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
248     if (eventLogSampleRate != 0 &&  //3 some cases print event log. And control the speed.
249         (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
250       member_signature.LogAccessToEventLog(access_method, action);
251     }
252   }
253 
254   if (action == kDeny) {  //  action is a direct return of refusal.
255     // Block access
256     return action;
257   }
258 
259   // Allow access to this member but print a warning.
260   DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast);
261 
262   if (access_method != kNone) {  //Print warning
263     // Depending on a runtime flag, we might move the member into whitelist and
264     // skip the warning the next time the member is accessed.
265     MaybeWhitelistMember(runtime, member);
266 
267     // If this action requires a UI warning, set the appropriate flag.
268     if (shouldWarn &&
269         (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
270       runtime->SetPendingHiddenApiWarning(true);
271     }
272   }
273 
274   return action;
275 }

As you can see from the code above, the GetMemberActionImpl function is mainly used to print log and add whitelist for different action.

Thus, the final function is GetActionFromAccessFlags function.

Our problem is generally clear here.

The whole framework is based on a flags in the method to get the corresponding execution action, and there are two more points, where flags are set, and how hidden_api_policy_is set.

 

posted on 2019-04-24 11:09  kelisi_king  阅读(765)  评论(0编辑  收藏  举报