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