Home | History | Annotate | Download | only in cts
      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
     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
     80     protected void tearDown() throws Exception {
     81         Os.prctl(OsConstants.PR_SET_DUMPABLE, dumpable, 0, 0, 0);
     82         super.tearDown();
     83     }
     84 
     85     @MediumTest
     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
    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
    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
    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
    152     public void testDataDirectoryNotWritable() throws Exception {
    153         assertFalse(isDirectoryWritable(Environment.getDataDirectory()));
    154     }
    155 
    156     @MediumTest
    157     public void testAndroidRootDirectoryNotWritable() throws Exception {
    158         assertFalse(isDirectoryWritable(Environment.getRootDirectory()));
    159     }
    160 
    161     @MediumTest
    162     public void testDownloadCacheDirectoryNotWritable() throws Exception {
    163         assertFalse(isDirectoryWritable(Environment.getDownloadCacheDirectory()));
    164     }
    165 
    166     @MediumTest
    167     public void testRootDirectoryNotWritable() throws Exception {
    168         assertFalse(isDirectoryWritable(new File("/")));
    169     }
    170 
    171     @MediumTest
    172     public void testDevDirectoryNotWritable() throws Exception {
    173         assertFalse(isDirectoryWritable(new File("/dev")));
    174     }
    175 
    176     @MediumTest
    177     public void testProcDirectoryNotWritable() throws Exception {
    178         assertFalse(isDirectoryWritable(new File("/proc")));
    179     }
    180 
    181     @MediumTest
    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
    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
    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
    208     public void testDevMemSane() throws Exception {
    209         File f = new File("/dev/mem");
    210         assertFalse(f.exists());
    211     }
    212 
    213     @MediumTest
    214     public void testDevkmemSane() throws Exception {
    215         File f = new File("/dev/kmem");
    216         assertFalse(f.exists());
    217     }
    218 
    219     @MediumTest
    220     public void testDevPortSane() throws Exception {
    221         File f = new File("/dev/port");
    222         assertFalse(f.exists());
    223     }
    224 
    225     @MediumTest
    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
    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
    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
    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
    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
    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 
    291     private static int readInt(File f) throws FileNotFoundException {
    292         try (Scanner s = new Scanner(f)) {
    293             return s.nextInt();
    294         }
    295     }
    296 
    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
    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
    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 
    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 
    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
    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")
    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
    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
    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      */
    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      */
    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
    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
    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
    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
    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 
    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
    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
    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
    783     public void testReadingSysFilesDoesntFail() throws Exception {
    784         ExecutorService executor = Executors.newCachedThreadPool();
    785         tryToReadFromAllIn(new File("/sys"), executor);
    786         executor.shutdownNow();
    787     }
    788 
    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 
    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 
    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
    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>
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
   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 
   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 
   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      */
   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>
   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 
   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 
   1168     private static boolean isSymbolicLink(File f) throws IOException {
   1169         return !f.getAbsolutePath().equals(f.getCanonicalPath());
   1170     }
   1171 
   1172 }
   1173