• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 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.tools.metalava
17 
18 import java.io.File
19 import java.io.FileDescriptor
20 import java.net.InetAddress
21 import java.security.Permission
22 import kotlin.concurrent.getOrSet
23 
24 /**
25  * Detect access to files that not explicitly specified in the command line.
26  *
27  * This class detects reads on both files and directories. Directory accesses are logged by
28  * the driver in [run], which only logs it, but doesn't consider it an error.
29  *
30  * We do not prevent reads on directories that are not explicitly listed in the command line because
31  * metalava (or JVM, really) seems to read some system directories such as /usr/, etc., but this
32  * behavior may be JVM dependent so we do not want to have to explicitly include them.
33  * (Because, otherwise, when we update JVM, it may access different directories and we end up
34  * having to update the implicit allowed list.) As long as we don't read files, reading directories
35  * shouldn't (normally) affect the result, so we simply allow any directory reads.
36  */
37 internal object FileReadSandbox {
38     private var installed = false
39 
40     private var previousSecurityManager: SecurityManager? = null
41     private val mySecurityManager = MySecurityManager()
42 
43     /**
44      * Set of paths that are allowed to access. This is used with an exact match.
45      */
46     private var allowedPaths = mutableSetOf<String>()
47 
48     /**
49      * Set of path prefixes that are allowed to access.
50      */
51     private var allowedPathPrefixes = mutableSetOf<String>()
52 
53     interface Listener {
54         /** Called when a violation is detected with the absolute path. */
55         fun onViolation(absolutePath: String, isDirectory: Boolean)
56     }
57 
58     private var listener: Listener? = null
59 
60     init {
61         initialize()
62     }
63 
64     private fun initialize() {
65         allowedPaths.clear()
66         allowedPathPrefixes.clear()
67         listener = null
68 
69         // Allow certain directories by default.
70         // The sandbox detects all file accessed done by JVM implicitly too (e.g. JVM seems to
71         // access /dev/urandom on linux), so we need to allow them.
72         // At least on linux, there doesn't seem to be any access to files under /proc/ or /sys/,
73         // but they should be allowed anyway.
74         listOf("/dev", "/proc", "/sys").forEach {
75             allowAccess(File(it))
76         }
77         // Allow access to the directory containing the metalava's jar itself.
78         // (so even if metalava loads other jar files in the same directory, that wouldn't be a
79         // violation.)
80         FileReadSandbox::class.java.protectionDomain?.codeSource?.location?.toURI()?.path?.let {
81             allowAccess(File(it).parentFile)
82         }
83 
84         // Allow access to $JAVA_HOME.
85         // We also allow $ANDROID_JAVA_HOME, which is used in the Android platform build.
86         // (which is normally $ANDROID_BUILD_TOP + "/prebuilts/jdk/jdk11/linux-x86" as of writing.)
87         listOf(
88             "JAVA_HOME",
89             "ANDROID_JAVA_HOME"
90         ).forEach {
91             System.getenv(it)?.let { path ->
92                 allowAccess(File(path))
93             }
94         }
95         // JVM seems to use ~/.cache/
96         System.getenv("HOME")?.let {
97             allowAccess(File("$it/.cache"))
98         }
99     }
100 
101     /** Activate the sandbox. */
102     fun activate(listener: Listener) {
103         if (installed) {
104             throw IllegalStateException("Already activated")
105         }
106         previousSecurityManager = System.getSecurityManager()
107         System.setSecurityManager(mySecurityManager)
108         installed = true
109         this.listener = listener
110     }
111 
112     /** Deactivate the sandbox. */
113     fun deactivate() {
114         if (!installed) {
115             throw IllegalStateException("Not activated")
116         }
117         System.setSecurityManager(previousSecurityManager)
118         previousSecurityManager = null
119 
120         installed = false
121     }
122 
123     /** Reset all the state and re-initialize the sandbox. Only callable when not activated. */
124     fun reset() {
125         if (installed) {
126             throw IllegalStateException("Can't reset. Already activated")
127         }
128         initialize()
129     }
130 
131     /** Allows access to files or directories. */
132     fun allowAccess(files: List<File>): List<File> {
133         files.forEach { allowAccess(it) }
134         return files
135     }
136 
137     /** Allows access to a file or directory. */
138     fun <T : File?> allowAccess(file: T): T {
139         if (file == null) {
140             return file
141         }
142         val path = file.absolutePath
143         val added = allowedPaths.add(path)
144         if (file.isDirectory) {
145             // Even if it had already been added to allowedPaths (i.e. added == false),
146             // the directory might have just been created, so we still do it.
147             allowedPathPrefixes.add("$path/")
148         }
149         if (!added) {
150             // Already added; bail early.
151             return file
152         }
153 
154         // Allow all parent directories. But don't allow prefix accesses (== access to the
155         // directory itself is okay, but don't grant access to any files/directories under it).
156         var parent = file.parentFile
157         while (true) {
158             if (parent == null) {
159                 break
160             }
161             @Suppress("NAME_SHADOWING")
162             val path = parent.absolutePath
163             if (!allowedPaths.add(path)) {
164                 break // already added.
165             }
166             if (path == "/") {
167                 break
168             }
169             parent = parent.parentFile
170         }
171         return file
172     }
173 
174     fun isAccessAllowed(file: File): Boolean {
175         if (!file.exists()) {
176             return true
177         }
178         val absPath = file.absolutePath
179 
180         if (allowedPaths.contains(absPath)) {
181             return true
182         }
183         allowedPathPrefixes.forEach {
184             if (absPath.startsWith(it)) {
185                 return true
186             }
187         }
188         return false
189     }
190 
191     fun isDirectory(file: File): Boolean {
192         try {
193             temporaryExempt.set(true)
194             return file.isDirectory()
195         } finally {
196             temporaryExempt.set(false)
197         }
198     }
199 
200     fun isFile(file: File): Boolean {
201         try {
202             temporaryExempt.set(true)
203             return file.isFile()
204         } finally {
205             temporaryExempt.set(false)
206         }
207     }
208 
209     /** Used to skip all checks on any filesystem access made within the [check] method. */
210     private val temporaryExempt = ThreadLocal<Boolean>()
211 
212     private fun check(path: String) {
213         if (temporaryExempt.getOrSet { false }) {
214             return
215         }
216 
217         try {
218             temporaryExempt.set(true)
219 
220             val file = File(path)
221 
222             if (!isAccessAllowed(file)) {
223                 listener?.onViolation(file.absolutePath, file.isDirectory)
224             }
225         } finally {
226             temporaryExempt.set(false)
227         }
228     }
229 
230     /**
231      * Reading files that are created by metalava should be allowed, so we detect file writes to
232      * new files, and add them to the allowed path list.
233      */
234     private fun writeDetected(origPath: String?) {
235         origPath ?: return
236 
237         if (temporaryExempt.getOrSet { false }) {
238             return
239         }
240 
241         try {
242             temporaryExempt.set(true)
243 
244             val file = File(origPath)
245             if (file.exists()) {
246                 return // Ignore writes to an existing file / directory.
247             }
248             allowedPaths.add(file.absolutePath)
249         } finally {
250             temporaryExempt.set(false)
251         }
252     }
253 
254     private class MySecurityManager : SecurityManager() {
255         override fun checkRead(file: String) {
256             check(file)
257         }
258 
259         override fun checkRead(file: String, context: Any?) {
260             check(file)
261         }
262         override fun checkRead(p0: FileDescriptor?) {
263         }
264 
265         override fun checkDelete(p0: String?) {
266         }
267 
268         override fun checkPropertiesAccess() {
269         }
270 
271         override fun checkAccess(p0: Thread?) {
272         }
273 
274         override fun checkAccess(p0: ThreadGroup?) {
275         }
276 
277         override fun checkExec(p0: String?) {
278         }
279 
280         override fun checkListen(p0: Int) {
281         }
282 
283         override fun checkExit(p0: Int) {
284         }
285 
286         override fun checkLink(p0: String?) {
287         }
288 
289         override fun checkPropertyAccess(p0: String?) {
290         }
291 
292         override fun checkPackageDefinition(p0: String?) {
293         }
294 
295         override fun checkMulticast(p0: InetAddress?) {
296         }
297 
298         override fun checkMulticast(p0: InetAddress?, p1: Byte) {
299         }
300 
301         override fun checkPermission(p0: Permission?) {
302         }
303 
304         override fun checkPermission(p0: Permission?, p1: Any?) {
305         }
306 
307         override fun checkPackageAccess(p0: String?) {
308         }
309 
310         override fun checkAccept(p0: String?, p1: Int) {
311         }
312 
313         override fun checkSecurityAccess(p0: String?) {
314         }
315 
316         override fun checkWrite(p0: FileDescriptor?) {
317         }
318 
319         override fun checkWrite(file: String?) {
320             writeDetected(file)
321         }
322 
323         override fun checkPrintJobAccess() {
324         }
325 
326         override fun checkCreateClassLoader() {
327         }
328 
329         override fun checkConnect(p0: String?, p1: Int) {
330         }
331 
332         override fun checkConnect(p0: String?, p1: Int, p2: Any?) {
333         }
334 
335         override fun checkSetFactory() {
336         }
337     }
338 }
339