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