• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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