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