| /* |
| * Copyright (C) 2022 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_2022_20007; |
| |
| import static androidx.test.core.app.ApplicationProvider.getApplicationContext; |
| import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assume.assumeNoException; |
| import static org.junit.Assume.assumeNotNull; |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.SharedPreferences.OnSharedPreferenceChangeListener; |
| import android.util.Log; |
| |
| import androidx.test.runner.AndroidJUnit4; |
| import androidx.test.uiautomator.By; |
| import androidx.test.uiautomator.UiDevice; |
| import androidx.test.uiautomator.Until; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class DeviceTest { |
| private Context mContext = getApplicationContext(); |
| private UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); |
| private boolean mIsVulnerable = true; |
| private boolean mIsVulnerable2 = true; |
| private String mFirstPocActivityBounds = ""; |
| private String mSecondPocActivityBounds = ""; |
| private String mPocAttackerActivityBounds = ""; |
| private SharedPreferences mSharedPrefs = null; |
| |
| String getStringRes(int key) { |
| return mContext != null ? mContext.getResources().getString(key) : null; |
| } |
| |
| int getIntegerRes(int key) { |
| return mContext != null ? mContext.getResources().getInteger(key) : null; |
| } |
| |
| String getBounds(String activityName) throws Exception { |
| String output = |
| mDevice.executeShellCommand(mContext.getString(R.string.dumpsysCmd, activityName)); |
| output = output.substring(output.indexOf(getStringRes(R.string.mBounds)), |
| output.indexOf(")", output.indexOf(getStringRes(R.string.mBounds))) + 1); |
| return output; |
| } |
| |
| void launchMainActivity(int numActivities) { |
| final Intent intent = new Intent(mContext, PocMainActivity.class); |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY); |
| intent.putExtra(getStringRes(R.string.numActivities), numActivities); |
| mContext.startActivity(intent); |
| } |
| |
| void checkResult(String key) { |
| int result = mSharedPrefs.getInt(key, getIntegerRes(R.integer.assumptionFailure)); |
| assumeTrue( |
| getStringRes(R.string.assumptionFailureMessage) + mFirstPocActivityBounds + " " |
| + mSecondPocActivityBounds + " " + mPocAttackerActivityBounds, |
| result != getIntegerRes(R.integer.assumptionFailure)); |
| assertFalse( |
| getStringRes(R.string.failMessage) + mFirstPocActivityBounds + " " |
| + mSecondPocActivityBounds + " " + mPocAttackerActivityBounds, |
| mIsVulnerable && result == getIntegerRes(R.integer.fail)); |
| } |
| |
| @Test |
| public void testRaceCondition() { |
| final long timeoutSec = 30L; |
| try { |
| assumeNotNull(mContext); |
| launchMainActivity(getIntegerRes(R.integer.twoActivities)); |
| mSharedPrefs = mContext.getSharedPreferences(getStringRes(R.string.sharedPreferences), |
| Context.MODE_APPEND); |
| assumeNotNull(mSharedPrefs); |
| final Semaphore preferenceChanged = new Semaphore(0); |
| final Semaphore preferenceChanged2 = new Semaphore(0); |
| OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener() { |
| @Override |
| public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, |
| String key) { |
| if (key.equals(getStringRes(R.string.resultKey))) { |
| if (sharedPreferences.getInt(key, |
| getIntegerRes(R.integer.assumptionFailure)) == getIntegerRes( |
| R.integer.pass)) { |
| preferenceChanged.release(); |
| mIsVulnerable = false; |
| } |
| } else if (key.equals(getStringRes(R.string.resultKey2))) { |
| if (sharedPreferences.getInt(key, |
| getIntegerRes(R.integer.assumptionFailure)) == getIntegerRes( |
| R.integer.pass)) { |
| preferenceChanged2.release(); |
| mIsVulnerable2 = false; |
| } |
| } |
| } |
| }; |
| mSharedPrefs.registerOnSharedPreferenceChangeListener(listener); |
| preferenceChanged.tryAcquire(timeoutSec, TimeUnit.SECONDS); |
| |
| // Check if attacker activity is able to overlay victim activity |
| mFirstPocActivityBounds = getBounds(FirstPocActivity.class.getName()); |
| String attackerActivityName = getStringRes(R.string.attackerPkg) + "/." |
| + getStringRes(R.string.attackerActivity); |
| mPocAttackerActivityBounds = getBounds(attackerActivityName); |
| Log.e("DeviceTest", "mFirstPocActivityBounds=" + mFirstPocActivityBounds); |
| Log.e("DeviceTest", "mPocAttackerActivityBounds=" + mPocAttackerActivityBounds); |
| boolean isValidConfiguration = |
| mFirstPocActivityBounds.equals(mPocAttackerActivityBounds); |
| if (isValidConfiguration) { |
| checkResult(getStringRes(R.string.resultKey)); |
| } else { |
| // Device might have 2 task display areas. Detect vulnerability in this case. |
| mDevice.pressHome(); |
| assumeTrue(mDevice.wait(Until.gone(By.pkg(mContext.getPackageName())), timeoutSec)); |
| mIsVulnerable = true; |
| mIsVulnerable2 = true; |
| launchMainActivity(getIntegerRes(R.integer.threeActivities)); |
| preferenceChanged.tryAcquire(getIntegerRes(R.integer.permitCount), timeoutSec, |
| TimeUnit.SECONDS); |
| preferenceChanged2.tryAcquire(timeoutSec, TimeUnit.SECONDS); |
| |
| // check if attacker activity is able to overlay any of the victim activities |
| mFirstPocActivityBounds = getBounds(FirstPocActivity.class.getName()); |
| String secondActivityName = getStringRes(R.string.secondPocAppPkg) + "/." |
| + getStringRes(R.string.secondActivity); |
| mSecondPocActivityBounds = getBounds(secondActivityName); |
| mPocAttackerActivityBounds = getBounds(attackerActivityName); |
| Log.e("DeviceTest", "mFirstPocActivityBounds=" + mFirstPocActivityBounds); |
| Log.e("DeviceTest", "mSecondPocActivityBounds=" + mSecondPocActivityBounds); |
| Log.e("DeviceTest", "mPocAttackerActivityBounds=" + mPocAttackerActivityBounds); |
| isValidConfiguration = mFirstPocActivityBounds.equals(mPocAttackerActivityBounds); |
| boolean isValidConfiguration2 = |
| mSecondPocActivityBounds.equals(mPocAttackerActivityBounds); |
| assumeTrue( |
| getStringRes(R.string.boundsNotEqualMessage) + mFirstPocActivityBounds + " " |
| + mSecondPocActivityBounds + " " + mPocAttackerActivityBounds, |
| isValidConfiguration || isValidConfiguration2); |
| |
| if (isValidConfiguration) { |
| checkResult(getStringRes(R.string.resultKey)); |
| } else { |
| checkResult(getStringRes(R.string.resultKey2)); |
| } |
| } |
| } catch (Exception e) { |
| assumeNoException(e); |
| } |
| } |
| } |