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