1 /*
2  * Copyright (C) 2022 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.permission.access.appop
18 
19 import android.Manifest
20 import android.annotation.UserIdInt
21 import android.app.AppGlobals
22 import android.app.AppOpsManager
23 import android.content.pm.PackageManager
24 import android.os.Binder
25 import android.os.Handler
26 import android.os.RemoteException
27 import android.os.UserHandle
28 import android.util.SparseBooleanArray
29 import android.util.SparseIntArray
30 import com.android.internal.annotations.VisibleForTesting
31 import com.android.internal.util.ArrayUtils
32 import com.android.internal.util.function.pooled.PooledLambda
33 import com.android.server.appop.AppOpsCheckingServiceInterface
34 import com.android.server.appop.OnOpModeChangedListener
35 import com.android.server.permission.access.AccessCheckingService
36 import com.android.server.permission.access.AppOpUri
37 import com.android.server.permission.access.PackageUri
38 import com.android.server.permission.access.UidUri
39 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
40 import com.android.server.permission.access.util.hasBits
41 import libcore.util.EmptyArray
42 import java.io.PrintWriter
43 
44 class AppOpService(
45     private val service: AccessCheckingService
46 ) : AppOpsCheckingServiceInterface {
47     private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME)
48         as PackageAppOpPolicy
49     private val uidPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
50         as UidAppOpPolicy
51 
52     private val context = service.context
53     private lateinit var handler: Handler
54     private lateinit var lock: Any
55     private lateinit var switchedOps: IntMap<IntArray>
56 
57     fun initialize() {
58         // TODO(b/252883039): Wrong handler. Inject main thread handler here.
59         handler = Handler(context.mainLooper)
60         // TODO(b/252883039): Wrong lock object. Inject AppOpsService here.
61         lock = Any()
62 
63         switchedOps = IntMap()
64         for (switchedCode in 0 until AppOpsManager._NUM_OP) {
65             val switchCode = AppOpsManager.opToSwitch(switchedCode)
66             switchedOps.put(switchCode,
67                 ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode))
68         }
69     }
70 
71     @VisibleForTesting
72     override fun writeState() {
73         // TODO Not yet implemented
74     }
75 
76     override fun readState() {
77         // TODO Not yet implemented
78     }
79 
80     @VisibleForTesting
81     override fun shutdown() {
82         // TODO Not yet implemented
83     }
84 
85     override fun systemReady() {
86         // TODO Not yet implemented
87     }
88 
89     override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
90         return opNameMapToOpIntMap(getUidModes(uid))
91     }
92 
93     override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
94         return opNameMapToOpIntMap(getPackageModes(packageName, userId))
95     }
96 
97     override fun getUidMode(uid: Int, op: Int): Int {
98         val appId = UserHandle.getAppId(uid)
99         val userId = UserHandle.getUserId(uid)
100         val opName = AppOpsManager.opToPublicName(op)
101         return service.getState {
102             with(uidPolicy) { getAppOpMode(appId, userId, opName) }
103         }
104     }
105 
106     private fun getUidModes(uid: Int): IndexedMap<String, Int>? {
107         val appId = UserHandle.getAppId(uid)
108         val userId = UserHandle.getUserId(uid)
109         return service.getState {
110             with(uidPolicy) { getAppOpModes(appId, userId) }
111         }
112     }
113 
114     override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
115         val appId = UserHandle.getAppId(uid)
116         val userId = UserHandle.getUserId(uid)
117         val opName = AppOpsManager.opToPublicName(op)
118         var wasChanged = false
119         service.mutateState {
120             wasChanged = with(uidPolicy) { setAppOpMode(appId, userId, opName, mode) }
121         }
122         return wasChanged
123     }
124 
125     override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
126         val opName = AppOpsManager.opToPublicName(op)
127         return service.getState {
128             with(packagePolicy) { getAppOpMode(packageName, userId, opName) }
129         }
130     }
131 
132     private fun getPackageModes(
133         packageName: String,
134         userId: Int
135     ): IndexedMap<String, Int>? =
136         service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }
137 
138     override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
139         val opName = AppOpsManager.opToPublicName(op)
140         service.mutateState {
141             with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
142         }
143     }
144 
145     override fun removeUid(uid: Int) {
146         val appId = UserHandle.getAppId(uid)
147         val userId = UserHandle.getUserId(uid)
148         service.mutateState {
149             with(uidPolicy) { removeAppOpModes(appId, userId) }
150         }
151     }
152 
153     override fun removePackage(packageName: String, userId: Int): Boolean {
154         var wasChanged = false
155         service.mutateState {
156             wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) }
157         }
158         return wasChanged
159     }
160 
161     private fun opNameMapToOpIntMap(modes: IndexedMap<String, Int>?): SparseIntArray =
162         if (modes == null) {
163             SparseIntArray()
164         } else {
165             val opIntMap = SparseIntArray(modes.size)
166             modes.forEachIndexed { _, opName, opMode ->
167                 opIntMap.put(AppOpsManager.strOpToOp(opName), opMode)
168             }
169             opIntMap
170         }
171 
172     override fun areUidModesDefault(uid: Int): Boolean {
173         val modes = getUidModes(uid)
174         return modes == null || modes.isEmpty()
175     }
176 
177     override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
178         val modes = service.getState { getPackageModes(packageName, userId) }
179         return modes == null || modes.isEmpty()
180     }
181 
182     override fun clearAllModes() {
183         // We don't need to implement this because it's only called in AppOpsService#readState
184         // and we have our own persistence.
185     }
186 
187     // code -> listeners
188     private val opModeWatchers = IntMap<IndexedSet<OnOpModeChangedListener>>()
189 
190     // packageName -> listeners
191     private val packageModeWatchers = IndexedMap<String, IndexedSet<OnOpModeChangedListener>>()
192 
193     override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
194         synchronized(lock) {
195             opModeWatchers.getOrPut(op) { IndexedSet() } += changedListener
196         }
197     }
198 
199     override fun startWatchingPackageModeChanged(
200         changedListener: OnOpModeChangedListener,
201         packageName: String
202     ) {
203         synchronized(lock) {
204             packageModeWatchers.getOrPut(packageName) { IndexedSet() } += changedListener
205         }
206     }
207 
208     override fun removeListener(changedListener: OnOpModeChangedListener) {
209         synchronized(lock) {
210             opModeWatchers.removeAllIndexed { _, _, listeners ->
211                 listeners -= changedListener
212                 listeners.isEmpty()
213             }
214             packageModeWatchers.removeAllIndexed { _, _, listeners ->
215                 listeners -= changedListener
216                 listeners.isEmpty()
217             }
218         }
219     }
220 
221     override fun getOpModeChangedListeners(op: Int): IndexedSet<OnOpModeChangedListener> {
222         synchronized(lock) {
223             val listeners = opModeWatchers[op]
224             return if (listeners == null) {
225                 IndexedSet()
226             } else {
227                 IndexedSet(listeners)
228             }
229         }
230     }
231 
232     override fun getPackageModeChangedListeners(
233         packageName: String
234     ): IndexedSet<OnOpModeChangedListener> {
235         synchronized(lock) {
236             val listeners = packageModeWatchers[packageName]
237             return if (listeners == null) {
238                 IndexedSet()
239             } else {
240                 IndexedSet(listeners)
241             }
242         }
243     }
244 
245     override fun notifyWatchersOfChange(op: Int, uid: Int) {
246         val listeners = getOpModeChangedListeners(op)
247         listeners.forEachIndexed { _, listener ->
248             notifyOpChanged(listener, op, uid, null)
249         }
250     }
251 
252     override fun notifyOpChanged(
253         changedListener: OnOpModeChangedListener,
254         op: Int,
255         uid: Int,
256         packageName: String?
257     ) {
258         if (uid != UID_ANY &&
259             changedListener.watchingUid >= 0 &&
260             changedListener.watchingUid != uid
261         ) {
262             return
263         }
264 
265         // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
266         val switchedCodes = when (changedListener.watchedOpCode) {
267             ALL_OPS -> switchedOps.get(op)
268             AppOpsManager.OP_NONE -> intArrayOf(op)
269             else -> intArrayOf(changedListener.watchedOpCode)
270         }
271 
272         for (switchedCode in switchedCodes) {
273             // There are features watching for mode changes such as window manager
274             // and location manager which are in our process. The callbacks in these
275             // features may require permissions our remote caller does not have.
276             val identity = Binder.clearCallingIdentity()
277             try {
278                 if (!shouldIgnoreCallback(switchedCode, changedListener)) {
279                     changedListener.onOpModeChanged(switchedCode, uid, packageName)
280                 }
281             } catch (e: RemoteException) {
282                 /* ignore */
283             } finally {
284                 Binder.restoreCallingIdentity(identity)
285             }
286         }
287     }
288 
289     private fun shouldIgnoreCallback(op: Int, listener: OnOpModeChangedListener): Boolean {
290         // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
291         // as watcher should not use this to signal if the value is changed.
292         return AppOpsManager.opRestrictsRead(op) && context.checkPermission(
293             Manifest.permission.MANAGE_APPOPS,
294             listener.callingPid,
295             listener.callingUid
296         ) != PackageManager.PERMISSION_GRANTED
297     }
298 
299     /**
300      * Construct a map from each listener (listening to the given op, uid) to all of its associated
301      * packageNames (by reverse-indexing opModeWatchers and packageModeWatchers), then invoke
302      * notifyOpChanged for each listener.
303      */
304     override fun notifyOpChangedForAllPkgsInUid(
305         op: Int,
306         uid: Int,
307         onlyForeground: Boolean,
308         callbackToIgnore: OnOpModeChangedListener?
309     ) {
310         val uidPackageNames = getPackagesForUid(uid)
311         val callbackSpecs = IndexedMap<OnOpModeChangedListener, IndexedSet<String>>()
312 
313         fun associateListenerWithPackageNames(
314             listener: OnOpModeChangedListener,
315             packageNames: Array<String>
316         ) {
317             val listenerIsForeground =
318                 listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
319             if (onlyForeground && !listenerIsForeground) {
320                 return
321             }
322             val changedPackages = callbackSpecs.getOrPut(listener) { IndexedSet() }
323             changedPackages.addAll(packageNames)
324         }
325 
326         synchronized(lock) {
327             // Collect all listeners from opModeWatchers and pckageModeWatchers
328             val listeners = opModeWatchers[op]
329             listeners?.forEachIndexed { _, listener ->
330                 associateListenerWithPackageNames(listener, uidPackageNames)
331             }
332             uidPackageNames.forEachIndexed { _, uidPackageName ->
333                 val packageListeners = packageModeWatchers[uidPackageName]
334                 packageListeners?.forEachIndexed { _, listener ->
335                     associateListenerWithPackageNames(listener, arrayOf(uidPackageName))
336                 }
337             }
338             // Remove ignored listeners
339             if (callbackToIgnore != null) {
340                 callbackSpecs.remove(callbackToIgnore)
341             }
342         }
343 
344         // For each (listener, packageName) pair, invoke notifyOpChanged
345         callbackSpecs.forEachIndexed { _, listener, reportedPackageNames ->
346             reportedPackageNames.forEachIndexed { _, reportedPackageName ->
347                 handler.sendMessage(
348                     PooledLambda.obtainMessage(
349                         AppOpService::notifyOpChanged, this, listener,
350                         op, uid, reportedPackageName
351                     )
352                 )
353             }
354         }
355     }
356 
357     private fun getPackagesForUid(uid: Int): Array<String> {
358         // Very early during boot the package manager is not yet or not yet fully started. At this
359         // time there are no packages yet.
360         return try {
361             AppGlobals.getPackageManager()?.getPackagesForUid(uid) ?: EmptyArray.STRING
362         } catch (e: RemoteException) {
363             EmptyArray.STRING
364         }
365     }
366 
367     override fun evalForegroundUidOps(
368         uid: Int,
369         foregroundOps: SparseBooleanArray?
370     ): SparseBooleanArray? {
371         synchronized(lock) {
372             val uidModes = getUidModes(uid)
373             return evalForegroundOps(uidModes, foregroundOps)
374         }
375     }
376 
377     override fun evalForegroundPackageOps(
378         packageName: String,
379         foregroundOps: SparseBooleanArray?,
380         @UserIdInt userId: Int
381     ): SparseBooleanArray? {
382         synchronized(lock) {
383             val ops = service.getState { getPackageModes(packageName, userId) }
384             return evalForegroundOps(ops, foregroundOps)
385         }
386     }
387 
388     private fun evalForegroundOps(
389         ops: IndexedMap<String, Int>?,
390         foregroundOps: SparseBooleanArray?
391     ): SparseBooleanArray? {
392         var foregroundOps = foregroundOps
393         ops?.forEachIndexed { _, opName, opMode ->
394             if (opMode == AppOpsManager.MODE_FOREGROUND) {
395                 if (foregroundOps == null) {
396                     foregroundOps = SparseBooleanArray()
397                 }
398                 evalForegroundWatchers(opName, foregroundOps!!)
399             }
400         }
401         return foregroundOps
402     }
403 
404     private fun evalForegroundWatchers(opName: String, foregroundOps: SparseBooleanArray) {
405         val opCode = AppOpsManager.strOpToOp(opName)
406         val listeners = opModeWatchers[opCode]
407         val hasForegroundListeners = foregroundOps[opCode] || listeners?.anyIndexed { _, listener ->
408             listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
409         } ?: false
410         foregroundOps.put(opCode, hasForegroundListeners)
411     }
412 
413     override fun dumpListeners(
414         dumpOp: Int,
415         dumpUid: Int,
416         dumpPackage: String?,
417         printWriter: PrintWriter
418     ): Boolean {
419         var needSep = false
420         if (opModeWatchers.size() > 0) {
421             var printedHeader = false
422             opModeWatchers.forEachIndexed { _, op, modeChangedListenerSet ->
423                 if (dumpOp >= 0 && dumpOp != op) {
424                     return@forEachIndexed // continue
425                 }
426                 val opName = AppOpsManager.opToName(op)
427                 var printedOpHeader = false
428                 modeChangedListenerSet.forEachIndexed listenerLoop@ { listenerIndex, listener ->
429                     with(printWriter) {
430                         if (dumpPackage != null &&
431                             dumpUid != UserHandle.getAppId(listener.watchingUid)) {
432                             return@listenerLoop // continue
433                         }
434                         needSep = true
435                         if (!printedHeader) {
436                             println("  Op mode watchers:")
437                             printedHeader = true
438                         }
439                         if (!printedOpHeader) {
440                             print("    Op ")
441                             print(opName)
442                             println(":")
443                             printedOpHeader = true
444                         }
445                         print("      #")
446                         print(listenerIndex)
447                         print(opName)
448                         print(": ")
449                         println(listener.toString())
450                     }
451                 }
452             }
453         }
454 
455         if (packageModeWatchers.size > 0 && dumpOp < 0) {
456             var printedHeader = false
457             packageModeWatchers.forEachIndexed { _, packageName, listeners ->
458                 with(printWriter) {
459                     if (dumpPackage != null && dumpPackage != packageName) {
460                         return@forEachIndexed // continue
461                     }
462                     needSep = true
463                     if (!printedHeader) {
464                         println("  Package mode watchers:")
465                         printedHeader = true
466                     }
467                     print("    Pkg ")
468                     print(packageName)
469                     println(":")
470                     listeners.forEachIndexed { listenerIndex, listener ->
471                         print("      #")
472                         print(listenerIndex)
473                         print(": ")
474                         println(listener.toString())
475                     }
476                 }
477             }
478         }
479         return needSep
480     }
481 
482     companion object {
483         private val LOG_TAG = AppOpService::class.java.simpleName
484 
485         // Constant meaning that any UID should be matched when dispatching callbacks
486         private const val UID_ANY = -2
487 
488         // If watchedOpCode==ALL_OPS, notify for ops affected by the switch-op
489         private const val ALL_OPS = -2
490     }
491 }
492