1 /*
2  * Copyright (C) 2018 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 package com.android.server.appop;
17 
18 import static android.app.AppOpsManager.MODE_ALLOWED;
19 import static android.app.AppOpsManager.MODE_ERRORED;
20 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
21 import static android.app.AppOpsManager.OP_FLAGS_ALL;
22 import static android.app.AppOpsManager.OP_FLAG_SELF;
23 import static android.app.AppOpsManager.OP_READ_SMS;
24 import static android.app.AppOpsManager.OP_WIFI_SCAN;
25 import static android.app.AppOpsManager.OP_WRITE_SMS;
26 import static android.os.UserHandle.getUserId;
27 
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
34 
35 import static com.google.common.truth.Truth.assertThat;
36 import static com.google.common.truth.Truth.assertWithMessage;
37 
38 import static org.junit.Assert.assertNotEquals;
39 import static org.junit.Assert.assertNotNull;
40 import static org.mockito.ArgumentMatchers.any;
41 import static org.mockito.ArgumentMatchers.anyInt;
42 import static org.mockito.ArgumentMatchers.anyString;
43 import static org.mockito.ArgumentMatchers.eq;
44 import static org.mockito.ArgumentMatchers.nullable;
45 
46 import android.app.AppOpsManager;
47 import android.app.AppOpsManager.OpEntry;
48 import android.app.AppOpsManager.PackageOps;
49 import android.content.ContentResolver;
50 import android.content.Context;
51 import android.content.pm.PackageManagerInternal;
52 import android.os.Handler;
53 import android.os.HandlerThread;
54 import android.os.Process;
55 import android.provider.Settings;
56 import android.util.ArrayMap;
57 
58 import androidx.test.InstrumentationRegistry;
59 import androidx.test.filters.SmallTest;
60 import androidx.test.runner.AndroidJUnit4;
61 
62 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
63 import com.android.server.LocalManagerRegistry;
64 import com.android.server.LocalServices;
65 import com.android.server.pm.PackageManagerLocal;
66 import com.android.server.pm.UserManagerInternal;
67 import com.android.server.pm.pkg.AndroidPackage;
68 import com.android.server.pm.pkg.PackageState;
69 import com.android.server.pm.pkg.PackageStateInternal;
70 
71 import org.junit.After;
72 import org.junit.Before;
73 import org.junit.Test;
74 import org.junit.runner.RunWith;
75 import org.mockito.quality.Strictness;
76 
77 import java.io.File;
78 import java.util.Collections;
79 import java.util.List;
80 import java.util.Map;
81 
82 /**
83  * Unit tests for AppOpsService. Covers functionality that is difficult to test using CTS tests
84  * or for which we can write more detailed unit tests than CTS tests (because the internal APIs are
85  * more finegrained data than the public ones).
86  */
87 @SmallTest
88 @RunWith(AndroidJUnit4.class)
89 public class AppOpsServiceTest {
90 
91     private static final String TAG = AppOpsServiceTest.class.getSimpleName();
92     // State will be persisted into this XML file.
93     private static final String APP_OPS_FILENAME = "appops.test.xml";
94     private static final String APP_OPS_ACCESSES_FILENAME = "appops_accesses.test.xml";
95 
96     private static final Context sContext = InstrumentationRegistry.getTargetContext();
97     private static final String sMyPackageName = sContext.getOpPackageName();
98 
99     private File mStorageFile;
100     private File mRecentAccessesFile;
101     private Handler mHandler;
102     private AppOpsService mAppOpsService;
103     private int mMyUid;
104     private long mTestStartMillis;
105     private StaticMockitoSession mMockingSession;
106 
setupAppOpsService()107     private void setupAppOpsService() {
108         mAppOpsService = new AppOpsService(mRecentAccessesFile, mStorageFile, mHandler,
109                 spy(sContext));
110         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
111 
112         // Always approve all permission checks
113         doNothing().when(mAppOpsService.mContext).enforcePermission(anyString(), anyInt(),
114                 anyInt(), nullable(String.class));
115     }
116 
117     @Before
setUp()118     public void setUp() {
119         mStorageFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
120         mRecentAccessesFile = new File(sContext.getFilesDir(), APP_OPS_ACCESSES_FILENAME);
121         mStorageFile.delete();
122         mRecentAccessesFile.delete();
123 
124         HandlerThread handlerThread = new HandlerThread(TAG);
125         handlerThread.start();
126         mHandler = new Handler(handlerThread.getLooper());
127         mMyUid = Process.myUid();
128 
129         initializeStaticMocks();
130 
131         setupAppOpsService();
132 
133         mTestStartMillis = System.currentTimeMillis();
134     }
135 
136     @After
tearDown()137     public void tearDown() {
138         mAppOpsService.shutdown();
139 
140         mMockingSession.finishMocking();
141     }
142 
initializeStaticMocks()143     private void initializeStaticMocks() {
144         mMockingSession = mockitoSession()
145                 .strictness(Strictness.LENIENT)
146                 .spyStatic(LocalServices.class)
147                 .spyStatic(LocalManagerRegistry.class)
148                 .spyStatic(Settings.Global.class)
149                 .startMocking();
150 
151         // Mock LocalServices.getService(PackageManagerInternal.class).getPackageStateInternal
152         // and getPackage dependency needed by AppOpsService
153         PackageManagerInternal mockPackageManagerInternal = mock(PackageManagerInternal.class);
154         AndroidPackage mockMyPkg = mock(AndroidPackage.class);
155         when(mockMyPkg.getAttributions()).thenReturn(Collections.emptyList());
156         PackageStateInternal mockMyPSInternal = mock(PackageStateInternal.class);
157         when(mockMyPSInternal.isPrivileged()).thenReturn(false);
158         when(mockMyPSInternal.getAppId()).thenReturn(mMyUid);
159         when(mockMyPSInternal.getAndroidPackage()).thenReturn(mockMyPkg);
160 
161         when(mockPackageManagerInternal.getPackageStateInternal(sMyPackageName))
162                 .thenReturn(mockMyPSInternal);
163         when(mockPackageManagerInternal.getPackage(sMyPackageName)).thenReturn(mockMyPkg);
164         doReturn(mockPackageManagerInternal).when(
165                 () -> LocalServices.getService(PackageManagerInternal.class));
166 
167         PackageManagerLocal mockPackageManagerLocal = mock(PackageManagerLocal.class);
168         PackageManagerLocal.UnfilteredSnapshot mockUnfilteredSnapshot =
169                 mock(PackageManagerLocal.UnfilteredSnapshot.class);
170         PackageState mockMyPS = mock(PackageState.class);
171         ArrayMap<String, PackageState> packageStates = new ArrayMap<>();
172         packageStates.put(sMyPackageName, mockMyPS);
173         when(mockMyPS.getAppId()).thenReturn(mMyUid);
174         when(mockUnfilteredSnapshot.getPackageStates()).thenReturn(packageStates);
175         when(mockPackageManagerLocal.withUnfilteredSnapshot()).thenReturn(mockUnfilteredSnapshot);
176         doReturn(mockPackageManagerLocal).when(
177                 () -> LocalManagerRegistry.getManager(PackageManagerLocal.class));
178 
179         UserManagerInternal mockUserManagerInternal = mock(UserManagerInternal.class);
180         when(mockUserManagerInternal.getUserIds()).thenReturn(new int[] {getUserId(mMyUid)});
181         doReturn(mockUserManagerInternal).when(
182                 () -> LocalServices.getService(UserManagerInternal.class));
183 
184         // Mock behavior to use specific Settings.Global.APPOP_HISTORY_PARAMETERS
185         doReturn(null).when(() -> Settings.Global.getString(any(ContentResolver.class),
186                 eq(Settings.Global.APPOP_HISTORY_PARAMETERS)));
187     }
188 
189     @Test
testGetOpsForPackage_noOpsLogged()190     public void testGetOpsForPackage_noOpsLogged() {
191         assertThat(getLoggedOps()).isNull();
192     }
193 
194     @Test
testNoteOperationAndGetOpsForPackage()195     public void testNoteOperationAndGetOpsForPackage() {
196         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
197         mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
198 
199         // Note an op that's allowed.
200         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
201         List<PackageOps> loggedOps = getLoggedOps();
202         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
203 
204         // Note another op that's not allowed.
205         mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
206                 false);
207         loggedOps = getLoggedOps();
208         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
209         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
210     }
211 
212     /**
213      * Tests the scenario where an operation's permission is controlled by another operation.
214      * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the
215      * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed.
216      */
217     @Test
testNoteOperationAndGetOpsForPackage_controlledByDifferentOp()218     public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
219         // This op controls WIFI_SCAN
220         mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
221 
222         assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
223                 null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
224 
225         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
226                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
227 
228         // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
229         mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
230         assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
231                 null, false).getOpMode()).isEqualTo(MODE_ERRORED);
232 
233         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
234                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
235     }
236 
237     // Tests the dumping and restoring of the in-memory state to/from XML.
238     @Test
testStatePersistence()239     public void testStatePersistence() {
240         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
241         mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
242         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
243         mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
244                 false);
245 
246         mAppOpsService.shutdown();
247 
248         // Create a new app ops service which will initialize its state from XML.
249         setupAppOpsService();
250         mAppOpsService.readState();
251 
252         // Query the state of the 2nd service.
253         List<PackageOps> loggedOps = getLoggedOps();
254         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
255         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
256     }
257 
258     // Tests that ops are persisted during shutdown.
259     @Test
testShutdown()260     public void testShutdown() {
261         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
262         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
263         mAppOpsService.shutdown();
264 
265         // Create a new app ops service which will initialize its state from XML.
266         setupAppOpsService();
267 
268         // Query the state of the 2nd service.
269         List<PackageOps> loggedOps = getLoggedOps();
270         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
271     }
272 
273     @Test
testGetOpsForPackage()274     public void testGetOpsForPackage() {
275         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
276         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
277 
278         // Query all ops
279         List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
280                 mMyUid, sMyPackageName, null /* all ops */);
281         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
282 
283         // Query specific ops
284         loggedOps = mAppOpsService.getOpsForPackage(
285                 mMyUid, sMyPackageName, new int[]{OP_READ_SMS, OP_WRITE_SMS});
286         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
287 
288         // Query unknown UID
289         loggedOps = mAppOpsService.getOpsForPackage(mMyUid + 1, sMyPackageName, null /* all ops */);
290         assertThat(loggedOps).isNull();
291 
292         // Query unknown package name
293         loggedOps = mAppOpsService.getOpsForPackage(mMyUid, "fake.package", null /* all ops */);
294         assertThat(loggedOps).isNull();
295 
296         // Query op code that's not been logged
297         loggedOps = mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName,
298                 new int[]{OP_WRITE_SMS});
299         assertThat(loggedOps).isNull();
300     }
301 
302     @Test
testPackageRemoved()303     public void testPackageRemoved() {
304         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
305         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
306 
307         List<PackageOps> loggedOps = getLoggedOps();
308         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
309 
310         mAppOpsService.packageRemoved(mMyUid, sMyPackageName);
311         assertThat(getLoggedOps()).isNull();
312     }
313 
314 
315     /*
316     TODO ntmyren: re enable when we have time to rewrite test.
317     @Test
318     public void testPackageRemovedHistoricalOps() throws InterruptedException {
319         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
320         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
321 
322         AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
323         historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null,
324                 AppOpsManager.UID_STATE_PERSISTENT, 0, 1);
325 
326         mAppOpsService.addHistoricalOps(historicalOps);
327 
328         AtomicReference<AppOpsManager.HistoricalOps> resultOpsRef = new AtomicReference<>();
329         AtomicReference<CountDownLatch> latchRef = new AtomicReference<>(new CountDownLatch(1));
330         RemoteCallback callback = new RemoteCallback(result -> {
331             resultOpsRef.set(result.getParcelable(AppOpsManager.KEY_HISTORICAL_OPS));
332             latchRef.get().countDown();
333         });
334 
335         // First, do a fetch to ensure it's written
336         mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
337                 FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
338 
339         latchRef.get().await(5, TimeUnit.SECONDS);
340         assertThat(latchRef.get().getCount()).isEqualTo(0);
341         assertThat(resultOpsRef.get().isEmpty()).isFalse();
342 
343         // Then, check it's deleted on removal
344         mAppOpsService.packageRemoved(mMyUid, sMyPackageName);
345 
346         latchRef.set(new CountDownLatch(1));
347 
348         mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
349                 FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
350 
351         latchRef.get().await(5, TimeUnit.SECONDS);
352         assertThat(latchRef.get().getCount()).isEqualTo(0);
353         assertThat(resultOpsRef.get().isEmpty()).isTrue();
354     }
355      */
356 
357     @Test
testUidRemoved()358     public void testUidRemoved() {
359         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
360         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
361 
362         List<PackageOps> loggedOps = getLoggedOps();
363         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
364 
365         mAppOpsService.uidRemoved(mMyUid);
366         assertThat(getLoggedOps()).isNull();
367     }
368 
369     @Test
testUidStateInitializationDoesntClearState()370     public void testUidStateInitializationDoesntClearState() throws InterruptedException {
371         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
372         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
373         mAppOpsService.initializeUidStates();
374         List<PackageOps> ops = mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName,
375                 new int[]{OP_READ_SMS});
376         assertNotNull(ops);
377         for (int i = 0; i < ops.size(); i++) {
378             List<OpEntry> opEntries = ops.get(i).getOps();
379             for (int j = 0; j < opEntries.size(); j++) {
380                 Map<String, AppOpsManager.AttributedOpEntry> attributedOpEntries = opEntries.get(
381                         j).getAttributedOpEntries();
382                 assertNotEquals(-1, attributedOpEntries.get(null)
383                         .getLastAccessTime(OP_FLAG_SELF));
384             }
385         }
386     }
387 
getLoggedOps()388     private List<PackageOps> getLoggedOps() {
389         return mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName, null /* all ops */);
390     }
391 
assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis, long minRejectMillis, int mode)392     private void assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis,
393             long minRejectMillis, int mode) {
394 
395         boolean opLogged = false;
396         for (PackageOps pkgOps : loggedOps) {
397             assertWithMessage("Unexpected UID").that(mMyUid).isEqualTo(pkgOps.getUid());
398             assertWithMessage("Unexpected package name").that(sMyPackageName).isEqualTo(
399                     pkgOps.getPackageName());
400 
401             for (OpEntry opEntry : pkgOps.getOps()) {
402                 if (opCode != opEntry.getOp()) {
403                     continue;
404                 }
405                 opLogged = true;
406 
407                 assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode());
408                 if (minMillis > 0) {
409                     assertWithMessage("Unexpected timestamp")
410                             .that(opEntry.getLastAccessTime(OP_FLAGS_ALL)).isAtLeast(minMillis);
411                 }
412                 if (minRejectMillis > 0) {
413                     assertWithMessage("Unexpected rejection timestamp").that(
414                             opEntry.getLastRejectTime(OP_FLAGS_ALL)).isAtLeast(minRejectMillis);
415                 }
416             }
417         }
418         assertWithMessage("Op was not logged").that(opLogged).isTrue();
419     }
420 }
421