| /* |
| * Copyright (C) 2020 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; |
| |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assume.assumeThat; |
| import static org.hamcrest.Matchers.*; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.RemoteCallback; |
| import android.os.SystemClock; |
| import android.platform.test.annotations.AsbSecurityTest; |
| import android.test.AndroidTestCase; |
| import android.util.Log; |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.TimeUnit; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class CVE_2021_0339 { |
| |
| static final String TAG = CVE_2021_0339.class.getSimpleName(); |
| private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts"; |
| private static final String CALLBACK_KEY = "testactivitycallback"; |
| private static final String RESULT_KEY = "testactivityresult"; |
| static final int DURATION_RESULT_CODE = 1; |
| static final int MAX_TRANSITION_DURATION_MS = 3000; // internal max |
| static final int TIME_MEASUREMENT_DELAY_MS = 5000; // tolerance for lag. |
| |
| private void launchActivity(Class<? extends Activity> clazz, RemoteCallback cb) { |
| final Context context = InstrumentationRegistry.getInstrumentation().getContext(); |
| final Intent intent = new Intent(Intent.ACTION_MAIN); |
| intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName()); |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| intent.putExtra(CALLBACK_KEY, cb); |
| context.startActivity(intent); |
| } |
| |
| /** |
| * b/175817167 |
| * start the first activity and get the result from the remote callback |
| */ |
| @Test |
| @AsbSecurityTest(cveBugId = 145728687) |
| public void testPocCVE_2021_0339() throws Exception { |
| CompletableFuture<Integer> callbackReturn = new CompletableFuture<>(); |
| RemoteCallback cb = new RemoteCallback((Bundle result) -> |
| callbackReturn.complete(result.getInt(RESULT_KEY))); |
| launchActivity(FirstActivity.class, cb); // start activity with callback as intent extra |
| |
| // blocking while the remotecallback is unset |
| Log.i(TAG, "wait for callbackReturn.get(15)"); |
| int duration = callbackReturn.get(15, TimeUnit.SECONDS); |
| |
| // if we couldn't get the duration of secondactivity in firstactivity, the default is -1 |
| assumeThat(duration, not(equals(-1))); |
| |
| // the max duration after the fix is 3 seconds. |
| // we check to see that the total duration was less than 8 seconds after accounting for lag |
| assertThat(duration, |
| lessThan(MAX_TRANSITION_DURATION_MS + TIME_MEASUREMENT_DELAY_MS)); |
| } |
| |
| /** |
| * create an activity so that the second activity has something to animate from |
| * start the second activity and get the result |
| * return the result from the second activity in the remotecallback |
| */ |
| public static class FirstActivity extends Activity { |
| private RemoteCallback cb; |
| |
| @Override |
| public void onCreate(Bundle bundle) { |
| super.onCreate(bundle); |
| cb = (RemoteCallback) getIntent().getExtras().get(CALLBACK_KEY); |
| } |
| |
| @Override |
| public void onEnterAnimationComplete() { |
| Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete() start"); |
| super.onEnterAnimationComplete(); |
| Intent intent = new Intent(this, SecondActivity.class); |
| intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis()); |
| startActivityForResult(intent, DURATION_RESULT_CODE); |
| overridePendingTransition(R.anim.translate2,R.anim.translate1); |
| Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete() stop"); |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode,int resultCode, Intent data) { |
| Log.d(TAG,this.getLocalClassName()+" onActivityResult() start"); |
| super.onActivityResult(requestCode, resultCode, data); |
| if (requestCode == DURATION_RESULT_CODE && resultCode == RESULT_OK) { |
| // this is the result that we requested |
| int duration = data.getIntExtra("duration", -1); // get result from secondactivity |
| Log.d(TAG,this.getLocalClassName()+" onActivityResult() duration=" + duration); |
| Bundle res = new Bundle(); |
| res.putInt(RESULT_KEY, duration); |
| finish(); |
| cb.sendResult(res); // update callback in test |
| Log.d(TAG,this.getLocalClassName()+" onActivityResult() result sent"); |
| } |
| Log.d(TAG,this.getLocalClassName()+" onActivityResult() stop"); |
| } |
| } |
| |
| /** |
| * measure time since secondactivity start to secondactivity animation complete |
| * return the duration in the result |
| */ |
| public static class SecondActivity extends Activity{ |
| @Override |
| public void onEnterAnimationComplete() { |
| Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete() start"); |
| super.onEnterAnimationComplete(); |
| long completedTs = SystemClock.uptimeMillis(); |
| long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0); |
| int duration = (int)(completedTs - startedTs); |
| Log.d(TAG, this.getLocalClassName() |
| + " onEnterAnimationComplete() duration=" + Long.toString(duration)); |
| Intent durationIntent = new Intent(); |
| durationIntent.putExtra("duration", duration); |
| setResult(RESULT_OK, durationIntent); // set result for firstactivity |
| finish(); // firstactivity only gets the result when we finish |
| } |
| } |
| } |