1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.sdksandbox.verifier; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.Manifest; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.OutcomeReceiver; 29 30 import androidx.test.platform.app.InstrumentationRegistry; 31 32 import com.android.server.sdksandbox.DeviceSupportedBaseTest; 33 import com.android.server.sdksandbox.proto.Verifier.AllowedApi; 34 import com.android.server.sdksandbox.proto.Verifier.AllowedApisList; 35 import com.android.server.sdksandbox.verifier.SdkDexVerifier.VerificationResult; 36 37 import org.junit.Before; 38 import org.junit.Test; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.concurrent.TimeUnit; 45 46 public class SdkDexVerifierUnitTest extends DeviceSupportedBaseTest { 47 48 private static final String TEST_PACKAGENAME = "com.android.codeproviderresources_1"; 49 50 private static final List<AllowedApi> ALL_ALLOWED_APIS = 51 List.of( 52 AllowedApi.newBuilder().setClassName("Landroid/*").setAllow(true).build(), 53 AllowedApi.newBuilder().setClassName("Landroidx/*").setAllow(true).build(), 54 AllowedApi.newBuilder().setClassName("Lcom/android/*").setAllow(true).build(), 55 AllowedApi.newBuilder() 56 .setClassName("Lcom/google/android/*") 57 .setAllow(true) 58 .build(), 59 AllowedApi.newBuilder().setClassName("Ljava/*").setAllow(true).build(), 60 AllowedApi.newBuilder().setClassName("Ljavax/*").setAllow(true).build()); 61 62 private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); 63 64 private final Context mContext = 65 InstrumentationRegistry.getInstrumentation().getTargetContext(); 66 private final PackageManager mPackageManager = mContext.getPackageManager(); 67 private final SdkDexVerifier mVerifier = SdkDexVerifier.getInstance(); 68 69 @Before setUp()70 public void setUp() { 71 InstrumentationRegistry.getInstrumentation() 72 .getUiAutomation() 73 .adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL); 74 } 75 76 @Test getApiTokens_fullyQualifiedRule()77 public void getApiTokens_fullyQualifiedRule() { 78 final AllowedApi apiRuleFull = 79 AllowedApi.newBuilder() 80 .setClassName("Landroid/view/inputmethod/InputMethodManager") 81 .setMethodName("getCurrentInputMethodSubtype") 82 .addParameters("V") 83 .setReturnType("Landroid/view/inputmethod/InputMethodSubtype") 84 .build(); 85 String[] expectedKeys = { 86 "Landroid", 87 "view", 88 "inputmethod", 89 "InputMethodManager", 90 "getCurrentInputMethodSubtype", 91 "V", 92 "Landroid/view/inputmethod/InputMethodSubtype" 93 }; 94 95 String[] keys = mVerifier.getApiTokens(apiRuleFull); 96 97 assertThat(keys).isEqualTo(expectedKeys); 98 } 99 100 @Test getApiTokens_classAndMethodRule()101 public void getApiTokens_classAndMethodRule() { 102 final AllowedApi apiRuleClassMethod = 103 AllowedApi.newBuilder() 104 .setClassName("Landroid/view/inputmethod/InputMethodManager") 105 .setMethodName("getCurrentInputMethodSubtype") 106 .build(); 107 String[] expectedKeys = { 108 "Landroid", 109 "view", 110 "inputmethod", 111 "InputMethodManager", 112 "getCurrentInputMethodSubtype", 113 null 114 }; 115 116 String[] keys = mVerifier.getApiTokens(apiRuleClassMethod); 117 118 assertThat(keys).isEqualTo(expectedKeys); 119 } 120 121 @Test getApiTokens_multiParam()122 public void getApiTokens_multiParam() { 123 final AllowedApi apiRuleMultiParam = 124 AllowedApi.newBuilder() 125 .setClassName("Landroid/view/inputmethod/InputMethodManager") 126 .setMethodName("getInputMethodListAsUser") 127 .addParameters("I") // int, according to DEX TypeDescriptor Semantics 128 .addParameters("I") 129 .setReturnType("Ljava/util/List") 130 .build(); 131 String[] expectedKeys = { 132 "Landroid", 133 "view", 134 "inputmethod", 135 "InputMethodManager", 136 "getInputMethodListAsUser", 137 "I", 138 "I", 139 "Ljava/util/List" 140 }; 141 142 String[] keys = mVerifier.getApiTokens(apiRuleMultiParam); 143 144 assertThat(keys).isEqualTo(expectedKeys); 145 } 146 147 @Test getApiTokens_classReturn()148 public void getApiTokens_classReturn() { 149 final AllowedApi apiRuleClassReturn = 150 AllowedApi.newBuilder() 151 .setClassName("Landroid/view/inputmethod/InputMethodManager") 152 .setReturnType("Ljava/util/List") 153 .build(); 154 String[] expectedKeys = { 155 "Landroid", "view", "inputmethod", "InputMethodManager", null, "Ljava/util/List" 156 }; 157 158 String[] keys = mVerifier.getApiTokens(apiRuleClassReturn); 159 160 assertThat(keys).isEqualTo(expectedKeys); 161 } 162 163 @Test getApiTokens_classAndParams()164 public void getApiTokens_classAndParams() { 165 final AllowedApi apiRuleClassParam = 166 AllowedApi.newBuilder() 167 .setClassName("Landroid/view/inputmethod/InputMethodManager") 168 .addParameters("V") 169 .build(); 170 String[] expectedKeys = { 171 "Landroid", "view", "inputmethod", "InputMethodManager", null, "V", null 172 }; 173 174 String[] keys = mVerifier.getApiTokens(apiRuleClassParam); 175 176 assertThat(keys).isEqualTo(expectedKeys); 177 } 178 179 @Test startDexVerification_loadApisFails()180 public void startDexVerification_loadApisFails() throws Exception { 181 ApiAllowlistProvider failAllowlistProvider = 182 new ApiAllowlistProvider("allowlist_doesn't_exist"); 183 TestOutcomeReceiver callback = new TestOutcomeReceiver(); 184 SdkDexVerifier verifier = 185 new SdkDexVerifier( 186 new SdkDexVerifier.Injector( 187 failAllowlistProvider, 188 new SerialDexLoader(new DexParserImpl(), MAIN_HANDLER))); 189 190 verifier.startDexVerification( 191 "apk_that_doesn't_get_verified", 192 "com.test.unverified_test_app", 193 33, 194 mContext, 195 callback); 196 197 assertThat(callback.getLastError()).isNotNull(); 198 assertThat(callback.getLastError().getMessage()) 199 .isEqualTo("allowlist_doesn't_exist not found."); 200 } 201 202 @Test startDexVerification_apkNotFound()203 public void startDexVerification_apkNotFound() throws Exception { 204 TestOutcomeReceiver callback = new TestOutcomeReceiver(); 205 206 mVerifier.startDexVerification( 207 "bogusPath", "com.test.nonexistent_test_app", 33, mContext, callback); 208 209 assertThat(callback.getLastError()).isNotNull(); 210 assertThat(callback.getLastError().getMessage()) 211 .isEqualTo("Apk to verify not found: bogusPath"); 212 } 213 214 @Test startDexVerification_passVerify()215 public void startDexVerification_passVerify() throws Exception { 216 AllowedApisList allowlist = 217 AllowedApisList.newBuilder().addAllAllowedApis(ALL_ALLOWED_APIS).build(); 218 ApiAllowlistProvider fakeAllowlistProvider = new FakeAllowlistProvider(allowlist); 219 TestOutcomeReceiver callback = new TestOutcomeReceiver(); 220 SdkDexVerifier verifier = 221 new SdkDexVerifier( 222 new SdkDexVerifier.Injector( 223 fakeAllowlistProvider, 224 new SerialDexLoader(new DexParserImpl(), MAIN_HANDLER))); 225 226 verifier.startDexVerification( 227 getPackageLocation(TEST_PACKAGENAME), TEST_PACKAGENAME, 33, mContext, callback); 228 229 assertThat(callback.getResult().hasPassed()).isTrue(); 230 assertThat(callback.getResult().getRestrictedUsages()).isEmpty(); 231 } 232 233 @Test startDexVerification_failVerify()234 public void startDexVerification_failVerify() throws Exception { 235 ArrayList<AllowedApi> allowedApis = new ArrayList<AllowedApi>(ALL_ALLOWED_APIS); 236 allowedApis.add( 237 AllowedApi.newBuilder() 238 .setClassName("Ljava/lang/String/*") 239 .setAllow(false) 240 .build()); 241 AllowedApisList allowlist = 242 AllowedApisList.newBuilder().addAllAllowedApis(allowedApis).build(); 243 ApiAllowlistProvider fakeAllowlistProvider = new FakeAllowlistProvider(allowlist); 244 TestOutcomeReceiver callback = new TestOutcomeReceiver(); 245 SdkDexVerifier verifier = 246 new SdkDexVerifier( 247 new SdkDexVerifier.Injector( 248 fakeAllowlistProvider, 249 new SerialDexLoader(new DexParserImpl(), MAIN_HANDLER))); 250 251 verifier.startDexVerification( 252 getPackageLocation(TEST_PACKAGENAME), TEST_PACKAGENAME, 33, mContext, callback); 253 254 assertThat(callback.getResult().hasPassed()).isFalse(); 255 assertThat(callback.getResult().getRestrictedUsages().size()).isGreaterThan(0); 256 assertThat(callback.getResult().getRestrictedUsages().get(0)) 257 .startsWith("Ljava/lang/String"); 258 } 259 getPackageLocation(String packageName)260 private String getPackageLocation(String packageName) throws Exception { 261 ApplicationInfo applicationInfo = 262 mPackageManager.getPackageInfo( 263 packageName, 264 PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES 265 | PackageManager.MATCH_ANY_USER) 266 .applicationInfo; 267 return applicationInfo.sourceDir; 268 } 269 270 private static class FakeAllowlistProvider extends ApiAllowlistProvider { 271 Map<Long, AllowedApisList> mFakeAllowlist; 272 FakeAllowlistProvider(AllowedApisList allowlist)273 FakeAllowlistProvider(AllowedApisList allowlist) { 274 super(); 275 mFakeAllowlist = Map.of(33L, allowlist); 276 } 277 278 @Override loadPlatformApiAllowlist()279 public Map<Long, AllowedApisList> loadPlatformApiAllowlist() { 280 return mFakeAllowlist; 281 } 282 } 283 284 private static class TestOutcomeReceiver 285 implements OutcomeReceiver<VerificationResult, Exception> { 286 private CountDownLatch mLatch = new CountDownLatch(1); 287 private Exception mLastError; 288 private VerificationResult mResult; 289 getLastError()290 public Exception getLastError() throws Exception { 291 assertWithMessage("Latch timed out").that(mLatch.await(5, TimeUnit.SECONDS)).isTrue(); 292 return mLastError; 293 } 294 getResult()295 public VerificationResult getResult() throws Exception { 296 assertWithMessage("Verification completed in 5 seconds") 297 .that(mLatch.await(5, TimeUnit.SECONDS)) 298 .isTrue(); 299 return mResult; 300 } 301 302 @Override onResult(VerificationResult result)303 public void onResult(VerificationResult result) { 304 mResult = result; 305 mLatch.countDown(); 306 } 307 308 @Override onError(Exception e)309 public void onError(Exception e) { 310 mLastError = e; 311 mLatch.countDown(); 312 } 313 } 314 } 315