1 /* 2 * Copyright (C) 2010 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 android.permission.cts; 18 19 import android.content.pm.ApplicationInfo; 20 import android.content.pm.PackageManager; 21 import android.os.Environment; 22 import android.platform.test.annotations.AppModeFull; 23 import android.system.ErrnoException; 24 import android.system.Os; 25 import android.system.OsConstants; 26 import android.system.StructStatVfs; 27 import android.test.AndroidTestCase; 28 import android.test.suitebuilder.annotation.MediumTest; 29 import android.test.suitebuilder.annotation.LargeTest; 30 import android.util.Pair; 31 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.FileDescriptor; 35 import java.io.FileFilter; 36 import java.io.FileInputStream; 37 import java.io.FileNotFoundException; 38 import java.io.FileOutputStream; 39 import java.io.FileReader; 40 import java.io.InputStream; 41 import java.io.IOException; 42 import java.io.OutputStream; 43 import java.math.BigInteger; 44 import java.nio.ByteBuffer; 45 import java.nio.ByteOrder; 46 import java.util.concurrent.Callable; 47 import java.util.concurrent.ExecutionException; 48 import java.util.concurrent.Executors; 49 import java.util.concurrent.ExecutorService; 50 import java.util.concurrent.Future; 51 import java.util.concurrent.TimeoutException; 52 import java.util.concurrent.TimeUnit; 53 import java.util.regex.Matcher; 54 import java.util.regex.Pattern; 55 import java.util.Arrays; 56 import java.util.HashMap; 57 import java.util.HashSet; 58 import java.util.LinkedList; 59 import java.util.List; 60 import java.util.Scanner; 61 import java.util.Set; 62 63 /** 64 * Verify certain permissions on the filesystem 65 * 66 * TODO: Combine this file with {@link android.os.cts.FileAccessPermissionTest} 67 */ 68 public class FileSystemPermissionTest extends AndroidTestCase { 69 70 private int dumpable; 71 72 @Override setUp()73 protected void setUp() throws Exception { 74 super.setUp(); 75 dumpable = Os.prctl(OsConstants.PR_GET_DUMPABLE, 0, 0, 0, 0); 76 Os.prctl(OsConstants.PR_SET_DUMPABLE, 1, 0, 0, 0); 77 } 78 79 @Override tearDown()80 protected void tearDown() throws Exception { 81 Os.prctl(OsConstants.PR_SET_DUMPABLE, dumpable, 0, 0, 0); 82 super.tearDown(); 83 } 84 85 @MediumTest testCreateFileHasSanePermissions()86 public void testCreateFileHasSanePermissions() throws Exception { 87 File myFile = new File(getContext().getFilesDir(), "hello"); 88 FileOutputStream stream = new FileOutputStream(myFile); 89 stream.write("hello world".getBytes()); 90 stream.close(); 91 try { 92 FileUtils.FileStatus status = new FileUtils.FileStatus(); 93 FileUtils.getFileStatus(myFile.getAbsolutePath(), status, false); 94 int expectedPerms = FileUtils.S_IFREG 95 | FileUtils.S_IWUSR 96 | FileUtils.S_IRUSR; 97 assertEquals( 98 "Newly created files should have 0600 permissions", 99 Integer.toOctalString(expectedPerms), 100 Integer.toOctalString(status.mode)); 101 } finally { 102 assertTrue(myFile.delete()); 103 } 104 } 105 106 @MediumTest testCreateDirectoryHasSanePermissions()107 public void testCreateDirectoryHasSanePermissions() throws Exception { 108 File myDir = new File(getContext().getFilesDir(), "helloDirectory"); 109 assertTrue(myDir.mkdir()); 110 try { 111 FileUtils.FileStatus status = new FileUtils.FileStatus(); 112 FileUtils.getFileStatus(myDir.getAbsolutePath(), status, false); 113 int expectedPerms = FileUtils.S_IFDIR 114 | FileUtils.S_IWUSR 115 | FileUtils.S_IRUSR 116 | FileUtils.S_IXUSR; 117 assertEquals( 118 "Newly created directories should have 0700 permissions", 119 Integer.toOctalString(expectedPerms), 120 Integer.toOctalString(status.mode)); 121 122 } finally { 123 assertTrue(myDir.delete()); 124 } 125 } 126 127 @MediumTest testOtherApplicationDirectoriesAreNotWritable()128 public void testOtherApplicationDirectoriesAreNotWritable() throws Exception { 129 Set<File> writableDirs = new HashSet<File>(); 130 List<ApplicationInfo> apps = getContext() 131 .getPackageManager() 132 .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES); 133 String myAppDirectory = getContext().getApplicationInfo().dataDir; 134 for (ApplicationInfo app : apps) { 135 if (app.dataDir != null && !myAppDirectory.equals(app.dataDir)) { 136 writableDirs.addAll(getWritableDirectoriesAndSubdirectoriesOf(new File(app.dataDir))); 137 } 138 } 139 140 assertTrue("Found writable directories: " + writableDirs.toString(), 141 writableDirs.isEmpty()); 142 } 143 144 @MediumTest testApplicationParentDirectoryNotWritable()145 public void testApplicationParentDirectoryNotWritable() throws Exception { 146 String myDataDir = getContext().getApplicationInfo().dataDir; 147 File parentDir = new File(myDataDir).getParentFile(); 148 assertFalse(parentDir.toString(), isDirectoryWritable(parentDir)); 149 } 150 151 @MediumTest testDataDirectoryNotWritable()152 public void testDataDirectoryNotWritable() throws Exception { 153 assertFalse(isDirectoryWritable(Environment.getDataDirectory())); 154 } 155 156 @MediumTest testAndroidRootDirectoryNotWritable()157 public void testAndroidRootDirectoryNotWritable() throws Exception { 158 assertFalse(isDirectoryWritable(Environment.getRootDirectory())); 159 } 160 161 @MediumTest testDownloadCacheDirectoryNotWritable()162 public void testDownloadCacheDirectoryNotWritable() throws Exception { 163 assertFalse(isDirectoryWritable(Environment.getDownloadCacheDirectory())); 164 } 165 166 @MediumTest testRootDirectoryNotWritable()167 public void testRootDirectoryNotWritable() throws Exception { 168 assertFalse(isDirectoryWritable(new File("/"))); 169 } 170 171 @MediumTest testDevDirectoryNotWritable()172 public void testDevDirectoryNotWritable() throws Exception { 173 assertFalse(isDirectoryWritable(new File("/dev"))); 174 } 175 176 @MediumTest testProcDirectoryNotWritable()177 public void testProcDirectoryNotWritable() throws Exception { 178 assertFalse(isDirectoryWritable(new File("/proc"))); 179 } 180 181 @MediumTest testDevDiagSane()182 public void testDevDiagSane() throws Exception { 183 File f = new File("/dev/diag"); 184 assertFalse(f.canRead()); 185 assertFalse(f.canWrite()); 186 assertFalse(f.canExecute()); 187 } 188 189 /* b/26813932 */ 190 @MediumTest testProcInterruptsNotReadable()191 public void testProcInterruptsNotReadable() throws Exception { 192 File f = new File("/proc/interrupts"); 193 assertFalse(f.canRead()); 194 assertFalse(f.canWrite()); 195 assertFalse(f.canExecute()); 196 } 197 198 /* b/26813932 */ 199 @MediumTest testProcStatNotReadable()200 public void testProcStatNotReadable() throws Exception { 201 File f = new File("/proc/stat"); 202 assertFalse(f.canRead()); 203 assertFalse(f.canWrite()); 204 assertFalse(f.canExecute()); 205 } 206 207 @MediumTest testDevMemSane()208 public void testDevMemSane() throws Exception { 209 File f = new File("/dev/mem"); 210 assertFalse(f.exists()); 211 } 212 213 @MediumTest testDevkmemSane()214 public void testDevkmemSane() throws Exception { 215 File f = new File("/dev/kmem"); 216 assertFalse(f.exists()); 217 } 218 219 @MediumTest testDevPortSane()220 public void testDevPortSane() throws Exception { 221 File f = new File("/dev/port"); 222 assertFalse(f.exists()); 223 } 224 225 @MediumTest testPn544Sane()226 public void testPn544Sane() throws Exception { 227 File f = new File("/dev/pn544"); 228 assertFalse(f.canRead()); 229 assertFalse(f.canWrite()); 230 assertFalse(f.canExecute()); 231 232 assertFileOwnedBy(f, "nfc"); 233 assertFileOwnedByGroup(f, "nfc"); 234 } 235 236 @MediumTest testBcm2079xSane()237 public void testBcm2079xSane() throws Exception { 238 File f = new File("/dev/bcm2079x"); 239 assertFalse(f.canRead()); 240 assertFalse(f.canWrite()); 241 assertFalse(f.canExecute()); 242 243 assertFileOwnedBy(f, "nfc"); 244 assertFileOwnedByGroup(f, "nfc"); 245 } 246 247 @MediumTest testBcm2079xi2cSane()248 public void testBcm2079xi2cSane() throws Exception { 249 File f = new File("/dev/bcm2079x-i2c"); 250 assertFalse(f.canRead()); 251 assertFalse(f.canWrite()); 252 assertFalse(f.canExecute()); 253 254 assertFileOwnedBy(f, "nfc"); 255 assertFileOwnedByGroup(f, "nfc"); 256 } 257 258 @MediumTest testDevQtaguidSane()259 public void testDevQtaguidSane() throws Exception { 260 File f = new File("/dev/xt_qtaguid"); 261 assertFalse(f.canRead()); 262 assertFalse(f.canWrite()); 263 assertFalse(f.canExecute()); 264 265 assertFileOwnedBy(f, "root"); 266 assertFileOwnedByGroup(f, "root"); 267 } 268 269 @MediumTest testProcQtaguidCtrlSane()270 public void testProcQtaguidCtrlSane() throws Exception { 271 File f = new File("/proc/net/xt_qtaguid/ctrl"); 272 assertFalse(f.canRead()); 273 assertFalse(f.canWrite()); 274 assertFalse(f.canExecute()); 275 276 assertFileOwnedBy(f, "root"); 277 assertFileOwnedByGroup(f, "net_bw_acct"); 278 } 279 280 @MediumTest testProcQtaguidStatsSane()281 public void testProcQtaguidStatsSane() throws Exception { 282 File f = new File("/proc/net/xt_qtaguid/stats"); 283 assertFalse(f.canRead()); 284 assertFalse(f.canWrite()); 285 assertFalse(f.canExecute()); 286 287 assertFileOwnedBy(f, "root"); 288 assertFileOwnedByGroup(f, "net_bw_stats"); 289 } 290 readInt(File f)291 private static int readInt(File f) throws FileNotFoundException { 292 try (Scanner s = new Scanner(f)) { 293 return s.nextInt(); 294 } 295 } 296 writeInt(File f, int value)297 private static boolean writeInt(File f, int value) throws IOException { 298 try (FileOutputStream os = new FileOutputStream(f)) { 299 try { 300 os.write(Integer.toString(value).getBytes()); 301 return true; 302 } catch (IOException e) { 303 return false; 304 } 305 } 306 } 307 308 @MediumTest testProcSelfOomAdjSane()309 public void testProcSelfOomAdjSane() throws IOException { 310 final int OOM_DISABLE = -17; 311 312 File f = new File("/proc/self/oom_adj"); 313 assertTrue(f.canRead()); 314 assertFalse(f.canExecute()); 315 316 int oom_adj = readInt(f); 317 assertNotSame("unprivileged processes should not be unkillable", OOM_DISABLE, oom_adj); 318 if (f.canWrite()) 319 assertFalse("unprivileged processes should not be able to reduce their oom_adj value", 320 writeInt(f, oom_adj - 1)); 321 } 322 323 @MediumTest testProcSelfOomScoreAdjSane()324 public void testProcSelfOomScoreAdjSane() throws IOException { 325 final int OOM_SCORE_ADJ_MIN = -1000; 326 327 File f = new File("/proc/self/oom_score_adj"); 328 assertTrue(f.canRead()); 329 assertFalse(f.canExecute()); 330 331 int oom_score_adj = readInt(f); 332 assertNotSame("unprivileged processes should not be unkillable", OOM_SCORE_ADJ_MIN, oom_score_adj); 333 if (f.canWrite()) { 334 assertFalse( 335 "unprivileged processes should not be able to reduce their oom_score_adj value", 336 writeInt(f, oom_score_adj - 1)); 337 assertTrue( 338 "unprivileged processes should be able to increase their oom_score_adj value", 339 writeInt(f, oom_score_adj + 1)); 340 assertTrue("unprivileged processes should be able to restore their oom_score_adj value", 341 writeInt(f, oom_score_adj)); 342 } 343 } 344 mappedPageRanges()345 private static List<Pair<Long, Long>> mappedPageRanges() throws IOException { 346 final BigInteger PAGE_SIZE = new BigInteger("4096"); 347 348 final Pattern mapsPattern = Pattern.compile("^(\\p{XDigit}+)-(\\p{XDigit}+)"); 349 List<Pair<Long, Long>> ret = new LinkedList<>(); 350 351 BufferedReader reader = new BufferedReader(new FileReader("/proc/self/maps")); 352 String line; 353 try { 354 while ((line = reader.readLine()) != null) { 355 Matcher m = mapsPattern.matcher(line); 356 m.find(); 357 358 long start = new BigInteger(m.group(1), 16).divide(PAGE_SIZE).longValue(); 359 long end = new BigInteger(m.group(2), 16).divide(PAGE_SIZE).longValue(); 360 361 ret.add(new Pair<>(start, end)); 362 } 363 364 return ret; 365 } finally { 366 reader.close(); 367 } 368 } 369 pfnIsZero(FileDescriptor pagemap, long start, long end)370 private static boolean pfnIsZero(FileDescriptor pagemap, long start, long end) throws ErrnoException, IOException { 371 // Note: reads from /proc/self/pagemap *must* be 64-bit aligned. Use low-level android.system.Os routines to 372 // ensure this. 373 final int SIZEOF_U64 = 8; 374 final long PAGE_PRESENT = 1L << 63; 375 final long PFN_MASK = (1L << 55) - 1; 376 377 for (long page = start; page < end; page++) { 378 long offset = page * SIZEOF_U64; 379 long seek = Os.lseek(pagemap, offset, OsConstants.SEEK_SET); 380 if (offset != seek) 381 throw new IOException("lseek(" + offset + ") returned " + seek); 382 383 byte bytes[] = new byte[SIZEOF_U64]; 384 ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder()); 385 int read = Os.read(pagemap, buf); 386 387 if (read == 0) 388 // /proc/[pid]/maps may contain entries that are outside the process's VM space, 389 // like the [vectors] page on 32-bit ARM devices. In this case, seek() succeeds but 390 // read() returns 0. The kernel is telling us that there are no more pagemap 391 // entries to read, so we can stop here. 392 break; 393 else if (read != bytes.length) 394 throw new IOException("read(" + bytes.length + ") returned " + read); 395 396 buf.position(0); 397 long entry = buf.getLong(); 398 if ((entry & PAGE_PRESENT) == PAGE_PRESENT && (entry & PFN_MASK) != 0) 399 return false; 400 } 401 402 return true; 403 } 404 405 @MediumTest testProcSelfPagemapSane()406 public void testProcSelfPagemapSane() throws ErrnoException, IOException { 407 FileDescriptor pagemap = null; 408 try { 409 pagemap = Os.open("/proc/self/pagemap", OsConstants.O_RDONLY, 0); 410 411 for (Pair<Long, Long> range : mappedPageRanges()) 412 if (!pfnIsZero(pagemap, range.first, range.second)) 413 fail("Device is missing the following kernel security patch: " 414 + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=ab676b7d6fbf4b294bf198fb27ade5b0e865c7ce"); 415 } catch (ErrnoException e) { 416 if (e.errno == OsConstants.EPERM) 417 // expected before 4.2 418 return; 419 420 throw e; 421 } finally { 422 if (pagemap != null) 423 Os.close(pagemap); 424 } 425 } 426 427 @MediumTest 428 @AppModeFull(reason = "Instant Apps cannot access proc_net labeled files") testTcpDefaultRwndSane()429 public void testTcpDefaultRwndSane() throws Exception { 430 File f = new File("/proc/sys/net/ipv4/tcp_default_init_rwnd"); 431 assertTrue(f.canRead()); 432 assertFalse(f.canWrite()); 433 assertFalse(f.canExecute()); 434 435 assertFileOwnedBy(f, "root"); 436 assertFileOwnedByGroup(f, "root"); 437 } 438 439 @MediumTest testIdletimerDirectoryExistsAndSane()440 public void testIdletimerDirectoryExistsAndSane() throws Exception { 441 File dir = new File("/sys/class/xt_idletimer"); 442 assertTrue(dir.isDirectory()); 443 assertFalse(dir.canWrite()); 444 assertTrue(dir.canExecute()); 445 446 assertFileOwnedBy(dir, "root"); 447 assertFileOwnedByGroup(dir, "root"); 448 } 449 450 451 @MediumTest testProcfsMmapRndBitsExistsAndSane()452 public void testProcfsMmapRndBitsExistsAndSane() throws Exception { 453 String arch = System.getProperty("os.arch"); 454 boolean supported = false; 455 boolean supported_64 = false; 456 457 if (arch.equals("aarch64") || arch.equals("x86_64")) 458 supported_64 = true; 459 else if (arch.startsWith("arm") || arch.endsWith("86")) 460 supported = true; 461 462 /* 64-bit OS should support running 32-bit applications */ 463 if (supported_64) { 464 File f = new File("/proc/sys/vm/mmap_rnd_compat_bits"); 465 assertTrue(f.exists()); 466 assertFalse(f.canRead()); 467 assertFalse(f.canWrite()); 468 assertFalse(f.canExecute()); 469 } 470 471 if (supported_64 || supported) { 472 File f = new File("/proc/sys/vm/mmap_rnd_bits"); 473 assertTrue(f.exists()); 474 assertFalse(f.canRead()); 475 assertFalse(f.canWrite()); 476 assertFalse(f.canExecute()); 477 } 478 } 479 480 /** 481 * Assert that a file is owned by a specific owner. This is a noop if the 482 * file does not exist. 483 * 484 * @param file The file to check. 485 * @param expectedOwner The owner of the file. 486 */ assertFileOwnedBy(File file, String expectedOwner)487 private static void assertFileOwnedBy(File file, String expectedOwner) { 488 FileUtils.FileStatus status = new FileUtils.FileStatus(); 489 String path = file.getAbsolutePath(); 490 if (file.exists() && FileUtils.getFileStatus(path, status, true)) { 491 String actualOwner = FileUtils.getUserName(status.uid); 492 if (!expectedOwner.equals(actualOwner)) { 493 String msg = String.format("Wrong owner. Expected '%s', but found '%s' for %s.", 494 expectedOwner, actualOwner, path); 495 fail(msg); 496 } 497 } 498 } 499 500 /** 501 * Assert that a file is owned by a specific group. This is a noop if the 502 * file does not exist. 503 * 504 * @param file The file to check. 505 * @param expectedGroup The owner group of the file. 506 */ assertFileOwnedByGroup(File file, String expectedGroup)507 private static void assertFileOwnedByGroup(File file, String expectedGroup) { 508 FileUtils.FileStatus status = new FileUtils.FileStatus(); 509 String path = file.getAbsolutePath(); 510 if (file.exists() && FileUtils.getFileStatus(path, status, true)) { 511 String actualGroup = FileUtils.getGroupName(status.gid); 512 if (!expectedGroup.equals(actualGroup)) { 513 String msg = String.format("Wrong group. Expected '%s', but found '%s' for %s.", 514 expectedGroup, actualGroup, path); 515 fail(msg); 516 } 517 } 518 } 519 520 @MediumTest testTtyO3Sane()521 public void testTtyO3Sane() throws Exception { 522 File f = new File("/dev/ttyO3"); 523 assertFalse(f.canRead()); 524 assertFalse(f.canWrite()); 525 assertFalse(f.canExecute()); 526 } 527 528 @MediumTest testDataMediaSane()529 public void testDataMediaSane() throws Exception { 530 final File f = new File("/data/media"); 531 assertFalse(f.canRead()); 532 assertFalse(f.canWrite()); 533 assertFalse(f.canExecute()); 534 } 535 536 @MediumTest testMntShellSane()537 public void testMntShellSane() throws Exception { 538 final File f = new File("/mnt/shell"); 539 assertFalse(f.canRead()); 540 assertFalse(f.canWrite()); 541 assertFalse(f.canExecute()); 542 } 543 544 @MediumTest testMntSecureSane()545 public void testMntSecureSane() throws Exception { 546 final File f = new File("/mnt/secure"); 547 assertFalse(f.canRead()); 548 assertFalse(f.canWrite()); 549 assertFalse(f.canExecute()); 550 } 551 isDirectoryWritable(File directory)552 private static boolean isDirectoryWritable(File directory) { 553 File toCreate = new File(directory, "hello"); 554 try { 555 toCreate.createNewFile(); 556 return true; 557 } catch (IOException e) { 558 // It's expected we'll get a "Permission denied" exception. 559 } finally { 560 toCreate.delete(); 561 } 562 return false; 563 } 564 565 /** 566 * Verify that any publicly readable directories reachable from 567 * the root directory are not writable. An application should only be 568 * able to write to it's own home directory. World writable directories 569 * are a security hole because they enable a number of different attacks. 570 * <ul> 571 * <li><a href="http://en.wikipedia.org/wiki/Symlink_race">Symlink Races</a></li> 572 * <li>Data destruction by deleting or renaming files you don't own</li> 573 * <li>Data substitution by replacing trusted files with untrusted files</li> 574 * </ul> 575 * 576 * Note: Because not all directories are readable, this is a best-effort 577 * test only. Writable directories within unreadable subdirectories 578 * will NOT be detected by this code. 579 */ 580 @LargeTest testAllOtherDirectoriesNotWritable()581 public void testAllOtherDirectoriesNotWritable() throws Exception { 582 File start = new File("/"); 583 Set<File> writableDirs = getWritableDirectoriesAndSubdirectoriesOf(start); 584 585 assertTrue("Found writable directories: " + writableDirs.toString(), 586 writableDirs.isEmpty()); 587 } 588 589 private static final Set<String> OTHER_RANDOM_DIRECTORIES = new HashSet<String>( 590 Arrays.asList( 591 "/app-cache", 592 "/app-cache/ciq/socket", 593 "/cache/fotapkg", 594 "/cache/fotapkg/tmp", 595 "/data/_SamsungBnR_", 596 "/data/_SamsungBnR_/BR", 597 "/data/2nd-init", 598 "/data/amit", 599 "/data/anr", 600 "/data/app", 601 "/data/app-private", 602 "/data/backup", 603 "/data/battd", 604 "/data/bootlogo", 605 "/data/btips", 606 "/data/btips/TI", 607 "/data/btips/TI/opp", 608 "/data/cache", 609 "/data/calibration", 610 "/data/clipboard", 611 "/data/clp", 612 "/data/dalvik-cache", 613 "/data/data", 614 "/data/data/.drm", 615 "/data/data/.drm/.wmdrm", 616 "/data/data/cw", 617 "/data/data/com.android.htcprofile", 618 "/data/data/com.android.providers.drm/rights", 619 "/data/data/com.htc.android.qxdm2sd", 620 "/data/data/com.htc.android.qxdm2sd/bin", 621 "/data/data/com.htc.android.qxdm2sd/data", 622 "/data/data/com.htc.android.qxdm2sd/tmp", 623 "/data/data/com.htc.android.netlogger/data", 624 "/data/data/com.htc.messagecs/att", 625 "/data/data/com.htc.messagecs/pdu", 626 "/data/data/com.htc.loggers/bin", 627 "/data/data/com.htc.loggers/data", 628 "/data/data/com.htc.loggers/htclog", 629 "/data/data/com.htc.loggers/tmp", 630 "/data/data/com.htc.loggers/htcghost", 631 "/data/data/com.lge.ers/android", 632 "/data/data/com.lge.ers/arm9", 633 "/data/data/com.lge.ers/kernel", 634 "/data/data/com.lge.wmc", 635 "/data/data/com.redbend.vdmc/lib", 636 "/data/data/recovery", 637 "/data/data/recovery/HTCFOTA", 638 "/data/data/recovery/OMADM", 639 "/data/data/shared", 640 "/data/diag_logs", 641 "/data/dontpanic", 642 "/data/drm", 643 "/data/drm/fwdlock", 644 "/data/drm/IDM", 645 "/data/drm/IDM/HTTP", 646 "/data/drm/rights", 647 "/data/dump", 648 "/data/efslog", 649 "/data/emt", 650 "/data/factory", 651 "/data/fics", 652 "/data/fics/dev", 653 "/data/fota", 654 "/data/gps", 655 "/data/gps/log", 656 "/data/gps/var", 657 "/data/gps/var/run", 658 "/data/gpscfg", 659 "/data/hwvefs", 660 "/data/htcfs", 661 "/data/img", 662 "/data/install", 663 "/data/internal-device", 664 "/data/internal-device/DCIM", 665 "/data/last_alog", 666 "/data/last_klog", 667 "/data/local", 668 "/data/local/logs", 669 "/data/local/logs/kernel", 670 "/data/local/logs/logcat", 671 "/data/local/logs/resetlog", 672 "/data/local/logs/smem", 673 "/data/local/mono", 674 "/data/local/mono/pulse", 675 "/data/local/purple", 676 "/data/local/purple/sound", 677 "/data/local/rights", 678 "/data/local/rwsystag", 679 "/data/local/skel", 680 "/data/local/skel/default", 681 "/data/local/skel/defualt", // Mispelled "defualt" is intentional 682 "/data/local/tmp", 683 "/data/local/tmp/com.nuance.android.vsuite.vsuiteapp", 684 "/data/log", 685 "/data/logger", 686 "/data/logs", 687 "/data/logs/core", 688 "/data/lost+found", 689 "/data/mdl", 690 "/data/misc", 691 "/data/misc/bluetooth", 692 "/data/misc/bluetooth/logs", 693 "/data/misc/dhcp", 694 "/data/misc/lockscreen", 695 "/data/misc/sensor", 696 "/data/misc/webwidgets", 697 "/data/misc/webwidgets/chess", 698 "/data/misc/widgets", 699 "/data/misc/wifi", 700 "/data/misc/wifi/sockets", 701 "/data/misc/wimax", 702 "/data/misc/wimax/sockets", 703 "/data/misc/wminput", 704 "/data/misc/wpa_supplicant", 705 "/data/nv", 706 "/data/nvcam", 707 "/data/panic", 708 "/data/panicreports", 709 "/data/preinstall_md5", 710 "/data/property", 711 "/data/radio", 712 "/data/secure", 713 "/data/security", 714 "/data/sensors", 715 "/data/shared", 716 "/data/simcom", 717 "/data/simcom/btadd", 718 "/data/simcom/simlog", 719 "/data/system", 720 "/data/tmp", 721 "/data/tombstones", 722 "/data/tombstones/ramdump", 723 "/data/tpapi", 724 "/data/tpapi/etc", 725 "/data/tpapi/etc/tpa", 726 "/data/tpapi/etc/tpa/persistent", 727 "/data/tpapi/user.bin", 728 "/data/vpnch", 729 "/data/wapi", 730 "/data/wifi", 731 "/data/wimax", 732 "/data/wimax/log", 733 "/data/wiper", 734 "/data/wpstiles", 735 "/data/xt9", 736 "/dbdata/databases", 737 "/efs/.android", 738 "/mnt/sdcard", 739 "/mnt/usbdrive", 740 "/mnt_ext", 741 "/mnt_ext/badablk2", 742 "/mnt_ext/badablk3", 743 "/mnt_ext/cache", 744 "/mnt_ext/data", 745 "/system/etc/security/drm", 746 "/synthesis/hades", 747 "/synthesis/chimaira", 748 "/synthesis/shdisp", 749 "/synthesis/hdmi", 750 "/tmp" 751 ) 752 ); 753 754 /** 755 * Verify that directories not discoverable by 756 * testAllOtherDirectoriesNotWritable are not writable. An application 757 * should only be able to write to it's own home directory. World 758 * writable directories are a security hole because they enable a 759 * number of different attacks. 760 * <ul> 761 * <li><a href="http://en.wikipedia.org/wiki/Symlink_race">Symlink Races</a></li> 762 * <li>Data destruction by deleting or renaming files you don't own</li> 763 * <li>Data substitution by replacing trusted files with untrusted files</li> 764 * </ul> 765 * 766 * Because /data and /data/data are not readable, we blindly try to 767 * poke around in there looking for bad directories. There has to be 768 * a better way... 769 */ 770 @LargeTest testOtherRandomDirectoriesNotWritable()771 public void testOtherRandomDirectoriesNotWritable() throws Exception { 772 Set<File> writableDirs = new HashSet<File>(); 773 for (String dir : OTHER_RANDOM_DIRECTORIES) { 774 File start = new File(dir); 775 writableDirs.addAll(getWritableDirectoriesAndSubdirectoriesOf(start)); 776 } 777 778 assertTrue("Found writable directories: " + writableDirs.toString(), 779 writableDirs.isEmpty()); 780 } 781 782 @LargeTest testReadingSysFilesDoesntFail()783 public void testReadingSysFilesDoesntFail() throws Exception { 784 ExecutorService executor = Executors.newCachedThreadPool(); 785 tryToReadFromAllIn(new File("/sys"), executor); 786 executor.shutdownNow(); 787 } 788 tryToReadFromAllIn(File dir, ExecutorService executor)789 private static void tryToReadFromAllIn(File dir, ExecutorService executor) throws IOException { 790 assertTrue(dir.isDirectory()); 791 792 if (isSymbolicLink(dir)) { 793 // don't examine symbolic links. 794 return; 795 } 796 797 File[] files = dir.listFiles(); 798 799 if (files != null) { 800 for (File f : files) { 801 if (f.isDirectory()) { 802 tryToReadFromAllIn(f, executor); 803 } else { 804 tryFileOpenRead(f, executor); 805 } 806 } 807 } 808 } 809 tryFileOpenRead(final File f, ExecutorService executor)810 private static void tryFileOpenRead(final File f, ExecutorService executor) throws IOException { 811 // Callable requires stack variables to be final. 812 Callable<Boolean> readFile = new Callable<Boolean>() { 813 @Override 814 public Boolean call() throws Exception { 815 return tryFileRead(f); 816 } 817 }; 818 819 Boolean completed = false; 820 String fileName = null; 821 Future<Boolean> future = null; 822 try { 823 fileName = f.getCanonicalPath(); 824 825 future = executor.submit(readFile); 826 827 // Block, waiting no more than set seconds. 828 completed = future.get(3, TimeUnit.SECONDS); 829 } catch (TimeoutException e) { 830 System.out.println("TIMEOUT: " + fileName); 831 } catch (InterruptedException e) { 832 System.out.println("INTERRUPTED: " + fileName); 833 } catch (ExecutionException e) { 834 System.out.println("TASK WAS ABORTED BY EXCEPTION: " + fileName); 835 } catch (IOException e) { 836 // File.getCanonicalPath() will throw this. 837 } finally { 838 if (future != null) { 839 future.cancel(true); 840 } 841 } 842 } 843 tryFileRead(File f)844 private static Boolean tryFileRead(File f) { 845 byte[] b = new byte[1024]; 846 try { 847 System.out.println("looking at " + f.getCanonicalPath()); 848 849 FileInputStream fis = new FileInputStream(f); 850 while((fis.available() != 0) && (fis.read(b) != -1)) { 851 // throw away data 852 } 853 854 fis.close(); 855 } catch (IOException e) { 856 // ignore 857 } 858 return true; 859 } 860 861 private static final Set<File> SYS_EXCEPTIONS = new HashSet<File>( 862 Arrays.asList( 863 new File("/sys/kernel/debug/tracing/trace_marker"), 864 new File("/sys/fs/selinux/member"), 865 new File("/sys/fs/selinux/user"), 866 new File("/sys/fs/selinux/relabel"), 867 new File("/sys/fs/selinux/create"), 868 new File("/sys/fs/selinux/access"), 869 new File("/sys/fs/selinux/context"), 870 new File("/sys/fs/selinux/validatetrans") 871 )); 872 873 @LargeTest testAllFilesInSysAreNotWritable()874 public void testAllFilesInSysAreNotWritable() throws Exception { 875 Set<File> writable = getAllWritableFilesInDirAndSubDir(new File("/sys")); 876 writable.removeAll(SYS_EXCEPTIONS); 877 assertTrue("Found writable: " + writable.toString(), 878 writable.isEmpty()); 879 } 880 881 private static Set<File> getAllWritableFilesInDirAndSubDir(File dir)882 getAllWritableFilesInDirAndSubDir(File dir) throws Exception { 883 assertTrue(dir.isDirectory()); 884 Set<File> retval = new HashSet<File>(); 885 886 if (isSymbolicLink(dir)) { 887 // don't examine symbolic links. 888 return retval; 889 } 890 891 File[] subDirectories = dir.listFiles(new FileFilter() { 892 @Override public boolean accept(File pathname) { 893 return pathname.isDirectory(); 894 } 895 }); 896 897 898 /* recurse into subdirectories */ 899 if (subDirectories != null) { 900 for (File f : subDirectories) { 901 retval.addAll(getAllWritableFilesInDirAndSubDir(f)); 902 } 903 } 904 905 File[] filesInThisDirectory = dir.listFiles(new FileFilter() { 906 @Override public boolean accept(File pathname) { 907 return pathname.isFile(); 908 } 909 }); 910 if (filesInThisDirectory == null) { 911 return retval; 912 } 913 914 for (File f: filesInThisDirectory) { 915 if (f.canWrite()) { 916 retval.add(f.getCanonicalFile()); 917 } 918 } 919 return retval; 920 } 921 testSystemMountedRO()922 public void testSystemMountedRO() throws Exception { 923 StructStatVfs vfs = Os.statvfs("/system"); 924 assertTrue("/system is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0); 925 } 926 testRootMountedRO()927 public void testRootMountedRO() throws Exception { 928 StructStatVfs vfs = Os.statvfs("/"); 929 assertTrue("rootfs is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0); 930 } 931 testVendorMountedRO()932 public void testVendorMountedRO() throws Exception { 933 StructStatVfs vfs = Os.statvfs("/vendor"); 934 assertTrue("/vendor is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0); 935 } 936 testOdmMountedRO()937 public void testOdmMountedRO() throws Exception { 938 StructStatVfs vfs = Os.statvfs("/odm"); 939 assertTrue("/odm is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0); 940 } 941 testOemMountedRO()942 public void testOemMountedRO() throws Exception { 943 StructStatVfs vfs = Os.statvfs("/oem"); 944 assertTrue("/oem is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0); 945 } 946 testDataMountedNoSuidNoDev()947 public void testDataMountedNoSuidNoDev() throws Exception { 948 StructStatVfs vfs = Os.statvfs(getContext().getFilesDir().getAbsolutePath()); 949 assertTrue("/data is not mounted NOSUID", (vfs.f_flag & OsConstants.ST_NOSUID) != 0); 950 assertTrue("/data is not mounted NODEV", (vfs.f_flag & OsConstants.ST_NODEV) != 0); 951 } 952 testAllBlockDevicesAreSecure()953 public void testAllBlockDevicesAreSecure() throws Exception { 954 Set<File> insecure = getAllInsecureDevicesInDirAndSubdir(new File("/dev"), FileUtils.S_IFBLK); 955 assertTrue("Found insecure block devices: " + insecure.toString(), 956 insecure.isEmpty()); 957 } 958 testDevRandomWorldReadableAndWritable()959 public void testDevRandomWorldReadableAndWritable() throws Exception { 960 File f = new File("/dev/random"); 961 962 assertTrue(f + " cannot be opened for reading", canOpenForReading(f)); 963 assertTrue(f + " cannot be opened for writing", canOpenForWriting(f)); 964 965 FileUtils.FileStatus status = new FileUtils.FileStatus(); 966 assertTrue(FileUtils.getFileStatus(f.getPath(), status, false)); 967 assertTrue( 968 f + " not world-readable/writable. Actual mode: 0" 969 + Integer.toString(status.mode, 8), 970 (status.mode & 0666) == 0666); 971 } 972 testDevUrandomWorldReadableAndWritable()973 public void testDevUrandomWorldReadableAndWritable() throws Exception { 974 File f = new File("/dev/urandom"); 975 976 assertTrue(f + " cannot be opened for reading", canOpenForReading(f)); 977 assertTrue(f + " cannot be opened for writing", canOpenForWriting(f)); 978 979 FileUtils.FileStatus status = new FileUtils.FileStatus(); 980 assertTrue(FileUtils.getFileStatus(f.getPath(), status, false)); 981 assertTrue( 982 f + " not world-readable/writable. Actual mode: 0" 983 + Integer.toString(status.mode, 8), 984 (status.mode & 0666) == 0666); 985 } 986 testDevHwRandomLockedDown()987 public void testDevHwRandomLockedDown() throws Exception { 988 File f = new File("/dev/hw_random"); 989 if (!f.exists()) { 990 // HW RNG is not required to be exposed on all devices. 991 return; 992 } 993 994 assertFalse(f + " can be opened for reading", canOpenForReading(f)); 995 assertFalse(f + " can be opened for writing", canOpenForWriting(f)); 996 997 FileUtils.FileStatus status = new FileUtils.FileStatus(); 998 assertFalse("stat permitted on " + f, 999 FileUtils.getFileStatus(f.getPath(), status, false)); 1000 } 1001 canOpenForReading(File f)1002 private static boolean canOpenForReading(File f) { 1003 try (InputStream in = new FileInputStream(f)) { 1004 return true; 1005 } catch (IOException expected) { 1006 return false; 1007 } 1008 } 1009 canOpenForWriting(File f)1010 private static boolean canOpenForWriting(File f) { 1011 try (OutputStream out = new FileOutputStream(f)) { 1012 return true; 1013 } catch (IOException expected) { 1014 return false; 1015 } 1016 } 1017 testFileHasOnlyCapsThrowsOnInvalidCaps()1018 public void testFileHasOnlyCapsThrowsOnInvalidCaps() throws Exception { 1019 try { 1020 // Ensure negative cap id fails. 1021 new FileUtils.CapabilitySet() 1022 .add(-1) 1023 .fileHasOnly("/system/bin/run-as"); 1024 fail(); 1025 } 1026 catch (IllegalArgumentException e) { 1027 // expected 1028 } 1029 1030 try { 1031 // Ensure too-large cap throws. 1032 new FileUtils.CapabilitySet() 1033 .add(OsConstants.CAP_LAST_CAP + 1) 1034 .fileHasOnly("/system/bin/run-as"); 1035 fail(); 1036 } 1037 catch (IllegalArgumentException e) { 1038 // expected 1039 } 1040 } 1041 1042 /** 1043 * Test that the /system/bin/run-as command has setuid and setgid 1044 * attributes set on the file. If these calls fail, debugger 1045 * breakpoints for native code will not work as run-as will not 1046 * be able to perform required elevated-privilege functionality. 1047 */ testRunAsHasCorrectCapabilities()1048 public void testRunAsHasCorrectCapabilities() throws Exception { 1049 // ensure file is user and group read/executable 1050 String filename = "/system/bin/run-as"; 1051 FileUtils.FileStatus status = new FileUtils.FileStatus(); 1052 assertTrue(FileUtils.getFileStatus(filename, status, false)); 1053 assertTrue(status.hasModeFlag(FileUtils.S_IRUSR | FileUtils.S_IXUSR)); 1054 assertTrue(status.hasModeFlag(FileUtils.S_IRGRP | FileUtils.S_IXGRP)); 1055 1056 // ensure file owner/group is set correctly 1057 File f = new File(filename); 1058 assertFileOwnedBy(f, "root"); 1059 assertFileOwnedByGroup(f, "shell"); 1060 1061 // ensure file has setuid/setgid enabled 1062 assertTrue(FileUtils.hasSetUidCapability(filename)); 1063 assertTrue(FileUtils.hasSetGidCapability(filename)); 1064 1065 // ensure file has *only* setuid/setgid attributes enabled 1066 assertTrue(new FileUtils.CapabilitySet() 1067 .add(OsConstants.CAP_SETUID) 1068 .add(OsConstants.CAP_SETGID) 1069 .fileHasOnly("/system/bin/run-as")); 1070 } 1071 1072 private static Set<File> getAllInsecureDevicesInDirAndSubdir(File dir, int type)1073 getAllInsecureDevicesInDirAndSubdir(File dir, int type) throws Exception { 1074 assertTrue(dir.isDirectory()); 1075 Set<File> retval = new HashSet<File>(); 1076 1077 if (isSymbolicLink(dir)) { 1078 // don't examine symbolic links. 1079 return retval; 1080 } 1081 1082 File[] subDirectories = dir.listFiles(new FileFilter() { 1083 @Override public boolean accept(File pathname) { 1084 return pathname.isDirectory(); 1085 } 1086 }); 1087 1088 1089 /* recurse into subdirectories */ 1090 if (subDirectories != null) { 1091 for (File f : subDirectories) { 1092 retval.addAll(getAllInsecureDevicesInDirAndSubdir(f, type)); 1093 } 1094 } 1095 1096 File[] filesInThisDirectory = dir.listFiles(); 1097 if (filesInThisDirectory == null) { 1098 return retval; 1099 } 1100 1101 for (File f: filesInThisDirectory) { 1102 FileUtils.FileStatus status = new FileUtils.FileStatus(); 1103 FileUtils.getFileStatus(f.getAbsolutePath(), status, false); 1104 if (status.isOfType(type)) { 1105 if (f.canRead() || f.canWrite() || f.canExecute()) { 1106 retval.add(f); 1107 } 1108 if (status.uid == 2000) { 1109 // The shell user should not own any devices 1110 retval.add(f); 1111 } 1112 1113 // Don't allow devices owned by GIDs 1114 // accessible to non-privileged applications. 1115 if ((status.gid == 1007) // AID_LOG 1116 || (status.gid == 1015) // AID_SDCARD_RW 1117 || (status.gid == 1023) // AID_MEDIA_RW 1118 || (status.gid == 1028) // AID_SDCARD_R 1119 || (status.gid == 2000)) // AID_SHELL 1120 { 1121 if (status.hasModeFlag(FileUtils.S_IRGRP) 1122 || status.hasModeFlag(FileUtils.S_IWGRP) 1123 || status.hasModeFlag(FileUtils.S_IXGRP)) 1124 { 1125 retval.add(f); 1126 } 1127 } 1128 } 1129 } 1130 return retval; 1131 } 1132 getWritableDirectoriesAndSubdirectoriesOf(File dir)1133 private Set<File> getWritableDirectoriesAndSubdirectoriesOf(File dir) throws Exception { 1134 Set<File> retval = new HashSet<File>(); 1135 if (!dir.isDirectory()) { 1136 return retval; 1137 } 1138 1139 if (isSymbolicLink(dir)) { 1140 // don't examine symbolic links. 1141 return retval; 1142 } 1143 1144 String myHome = getContext().getApplicationInfo().dataDir; 1145 String thisDir = dir.getCanonicalPath(); 1146 if (thisDir.startsWith(myHome)) { 1147 // Don't examine directories within our home directory. 1148 // We expect these directories to be writable. 1149 return retval; 1150 } 1151 1152 if (isDirectoryWritable(dir)) { 1153 retval.add(dir); 1154 } 1155 1156 File[] subFiles = dir.listFiles(); 1157 if (subFiles == null) { 1158 return retval; 1159 } 1160 1161 for (File f : subFiles) { 1162 retval.addAll(getWritableDirectoriesAndSubdirectoriesOf(f)); 1163 } 1164 1165 return retval; 1166 } 1167 isSymbolicLink(File f)1168 private static boolean isSymbolicLink(File f) throws IOException { 1169 return !f.getAbsolutePath().equals(f.getCanonicalPath()); 1170 } 1171 1172 } 1173