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