Background
Information disclosure from binder IPC is no longer a new vulnerability among Android OEMs. This is mainly because these OEMs tend to develop features like multi-screen collaboration that involve modifications to "Window" and "Activity". However, the two identical vulnerabilities I encountered this time still intrigued me, so I’d like to share them with you.
The First Vulnerability
It all started when I came across an interface named getActivityConfigs. Its logic is roughly as follows (some information is redacted):
@Override // com.android.server.wm./* hide */
public Bundle getActivityConfigs(IBinder token, String pkgName) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_SMART_WINDOW, isNeedSmartWindow(pkgName) || isNeedTvSmartWindow(pkgName));
ActivityRecord activityRecord = null;
synchronized (this.mIAtmsInner.getATMS().getGlobalLock()) {
if (token != null) { // [1]
try {
activityRecord = ActivityRecord.isInRootTaskLocked(token);
} finally {
}
}
if (token == null && pkgName != null) {
activityRecord = this.mIAtmsInner.getLastResumedActivityRecord(); // [2]
}
// [3]
if (activityRecord != null && (TextUtils.equals(activityRecord.packageName, pkgName) ||
/* hide */)) {
/* hide */
Task task = activityRecord.getTask();
if (task != null) { // [4]
bundle.putBoolean(KEY_FIXED_SIZE, task.isFixedSize());
bundle.putBoolean("hasCaption", task.isHwStackShowCaption);
bundle.putBoolean(HwActivityTaskManager./* hide */, task.isAlwaysOnTop());
/* hide */
int policyMode = task.getWindowingMode();
bundle.putInt("android.activity.windowingMode", policyMode);
if (policyMode == 101) {
policyMode = 100;
}
if (policyMode == 102) {
policyMode = 102;
}
int displayId = activityRecord.getDisplayId();
bundle.putFloat(KEY_CORNER_RADIUS, getCornerRadius(policyMode, displayId));
bundle.putBoolean(KEY_CAPTION_BAR, isNeedCaptionBar(policyMode, displayId));
}
return bundle; // [5]
}
return bundle;
}
}
First of all, there is no permission check. But what can this interface do? At the marked point [1], it retrieves the IBinder token we passed in and passes it to isInRootTaskLocked. In fact, nothing can be done here normally, because it’s impossible to know the IBinder token of other components under normal circumstances. However, at the marked point [2], there is an "escape route": even if the token is null, as long as pkgName is not null, it will call getLastResumedActivityRecord to obtain the corresponding record. The explanation of LastResumedActivity in the AOSP code is:
The last resumed activity. This is identical to the current resumed activity most of the time but could be different when we’re pausing one activity before we resume another activity.
In simple terms, it refers to the Activity that is currently in the foreground of the user’s screen. What happens next is quite interesting: at the marked point [3], it checks whether the packageName of LastResumedActivity is the same as the input pkgName (the validity of the subsequent "OR" condition does not affect this check). If they are the same and activityRecord.getTask() is not null, it will write a lot of content into the bundle at the marked point [4] and return the bundle at the marked point [5].
Here, an attacker can iterate through all package names, pass them into this function, and poll to determine which package name corresponds to the current LastResumedActivity—thereby finding out which app is displayed in the foreground, resulting in information disclosure.
The "Copy-Pasted" Vulnerability
If it were just the first vulnerability, it wouldn’t be that interesting—it would be just another boring information disclosure vulnerability. However, when I was reviewing the code of another OEM in mainland China, I surprisingly found almost identical code. Moreover, judging from the affected versions, this vulnerability appeared later than the first one, which means it is very likely a "copy-pasted" vulnerability. To show you how "identical" they are, here is the code (some information is redacted):
public Bundle getActivityConfigs(IBinder token, String packageName) {
Task task;
Bundle bundle = new Bundle();
ActivityRecord activityRecord = null;
synchronized (this.mAtms.mGlobalLock) {
if (token != null) {
try {
activityRecord = ActivityRecord.isInRootTaskLocked(token);
} catch (Throwable th) {
throw th;
}
}
if (token == null && packageName != null) {
activityRecord = this.mAtms.mLastResumedActivity;
}
if (activityRecord != null && TextUtils.equals(activityRecord.packageName, packageName) && (task = activityRecord.getTask()) != null) {
/* hide */
bundle.putBoolean(/* hide */.KEY_SUPPORT_SPLIT_SCREEN_WINDOWING_MODE, isSupportMultiWindowMode);
bundle.putBoolean(/* hide */.KEY_LAUNCH_TASK_EMBEDDED, task.getWrapper().getExtImpl().isTaskEmbedded());
bundle.putInt(/* hide */.KEY_LAUNCH_TASK_SCENARIO, task.getWrapper().getExtImpl().getLaunchScenario());
bundle.putBoolean(/* hide */.KEY_SUPER_LOCK, task.getWrapper().getExtImpl().isSmartBackend());
}
}
return bundle;
}
Similarly, an attacker can poll through package names to obtain the app displayed in the foreground.
Postscript
The two vulnerabilities in this article are essentially a "sad" story of "copying code and copying the exact same vulnerability". Later, I reported both vulnerabilities to the corresponding OEMs, but the rewards I received were vastly different: the reward for the first one reached a five-digit figure, while the reward for the second one was only three digits. I will not name the specific brands—interested researchers can easily find out which two OEMs they are through their own research.
From the "copy-pasted" vulnerabilities to the huge gap in rewards, we can see the different attitudes of various OEMs towards innovation and security. Ultimately, these attitudes are reflected in the market as a huge gap in brand image.