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