• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.security.cts;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.os.storage.StorageManager;
22 import android.test.AndroidTestCase;
23 
24 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.io.UnsupportedEncodingException;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Scanner;
34 import java.util.Set;
35 
36 public class VoldExploitTest extends AndroidTestCase {
37 
38     /**
39      * Try to inject vold commands.
40      */
testTryCommandInjection()41     public void testTryCommandInjection() throws Exception {
42         final StorageManager sm = (StorageManager) getContext().getSystemService(
43                 Context.STORAGE_SERVICE);
44         String path = sm.getMountedObbPath("/dev/null\0asec list");
45         assertNull(path);
46     }
47 
48     /**
49      * Validate that this device isn't vulnerable to the "ZergRush"
50      * vold vulnerability (CVE-2011-3874).
51      *
52      * https://github.com/revolutionary/zergRush/blob/master/zergRush.c
53      *
54      * Note: If the ZergRush vulnerability is present, the call to
55      * {@link StorageManager#getMountedObbPath(String)} below hangs until CTS
56      * kills the testsuite (10 minutes). A timeout, while not desirable,
57      * is the typical failure for this test.
58      */
testZergRushCrash()59     public void testZergRushCrash() throws Exception {
60         int pid = Proc.findPidFor("/system/bin/vold");
61 
62         StorageManager sm = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
63         sm.getMountedObbPath("AAAA AAAA AAAA AAAA "
64                 + "AAAA AAAA AAAA AAAA "
65                 + "AAAA AAAA AAAA AAAA "
66                 + "AAAA AAAA AAAA AAAA"
67                 + "AAAA AAAA AAAA AAAA"
68                 + "AAAA AAAA AAAA AAAA"
69                 + "AAAA AAAA AAAA AAAA"
70                 + "AAAA AAAA AAAA AAAA");
71 
72         Thread.sleep(2000);  // give vold some time to crash
73 
74         // Check to see if vold is still alive.
75         assertTrue(
76                 "PID=" + pid + " crashed due to a malformed mount message."
77                         + " Detected unpatched ZergRush vulnerability (CVE-2011-3874).",
78                 new File("/proc/" + pid + "/cmdline").exists());
79     }
80 
81     /**
82      * Try to crash the vold program using CVE-2011-1823.
83      *
84      * This test attempts to send an invalid netlink messages to
85      * any process which is listening for the messages.  If we detect
86      * that any process crashed as a result of our message, then
87      * we know that we found a bug.
88      *
89      * If this test fails, it's due to CVE-2011-1823
90      *
91      * http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-1823
92      */
testTryToCrashVold()93     public void testTryToCrashVold() throws IOException {
94         Set<Integer> pids = getPids();
95         assertTrue(pids.size() > 1);  // at least vold and netd should exist
96 
97         Set<String> devices = new HashSet<String>();
98         devices.addAll(getSysFsPath("/etc/vold.fstab"));
99         devices.addAll(getSysFsPath("/system/etc/vold.fstab"));
100         if (devices.isEmpty()) {
101           // This vulnerability is not exploitable if there's
102           // no entry in vold.fstab
103           return;
104         }
105 
106         NetlinkSocket ns = NetlinkSocket.create();
107         for (int i : pids) {
108             for (String j : devices) {
109                 doAttack(ns, i, j);
110             }
111         }
112 
113         // Check to see if all the processes are still alive.  If
114         // any of them have died, we found an exploitable bug.
115         for (int i : pids) {
116             assertTrue(
117                     "PID=" + i + " crashed due to a malformed netlink message."
118                     + " Detected unpatched vulnerability CVE-2011-1823.",
119                     new File("/proc/" + i + "/cmdline").exists());
120         }
121     }
122 
123     /**
124      * Try to actually crash the program, by first sending a fake
125      * request to add a new disk, followed by a fake request to add
126      * a partition.
127      */
doAttack(NetlinkSocket ns, int pid, String path)128     private static void doAttack(NetlinkSocket ns, int pid, String path)
129             throws IOException {
130         try {
131             ns.sendmsg(pid, getDiskAddedMessage(path));
132             confirmNetlinkMsgReceived();
133 
134             for (int i = -1000; i > -5000; i-=1000) {
135                 ns.sendmsg(pid, getPartitionAddedMessage(path, i));
136                 confirmNetlinkMsgReceived();
137             }
138         } catch (IOException e) {
139             // Ignore the exception.  The process either:
140             //
141             // 1) Crashed
142             // 2) Closed the netlink socket and refused further messages
143             //
144             // If #1 occurs, our PID check in testTryToCrashVold() will
145             // detect the process crashed and trigger an error.
146             //
147             // #2 is not a security bug.  It's perfectly acceptable to
148             // refuse messages from someone trying to send you
149             // malicious content.
150         }
151     }
152 
153     /**
154      * Parse the fstab.vold file, and extract out the "sysfs_path" field.
155      */
getSysFsPath(String file)156     private static Set<String> getSysFsPath(String file) throws IOException {
157         Set<String> retval = new HashSet<String>();
158         File netlink = new File(file);
159         if (!netlink.canRead()) {
160             return retval;
161         }
162         Scanner scanner = null;
163         try {
164             scanner = new Scanner(netlink);
165             while(scanner.hasNextLine()) {
166                 String line = scanner.nextLine().trim();
167                 if (!line.startsWith("dev_mount")) {
168                     continue;
169                 }
170 
171                 String[] fields = line.split("\\s+");
172                 assertTrue(fields.length >= 5);
173                 // Column 5 and beyond is "sysfs_path"
174                 retval.addAll(Arrays.asList(fields).subList(4, fields.length));
175             }
176         } finally {
177             if (scanner != null) {
178                 scanner.close();
179             }
180         }
181         return retval;
182     }
183 
184     /**
185      * Poll /proc/net/netlink until all the "Rmem" fields contain
186      * "0" or approximately 10 seconds have passed.
187      *
188      * This indicates that either the netlink message was received,
189      * or the process took too long to process the incoming netlink
190      * message.
191      *
192      * See http://code.google.com/p/android/issues/detail?id=25099
193      * for information on why the timeout is needed.
194      */
confirmNetlinkMsgReceived()195     private static void confirmNetlinkMsgReceived() {
196         try {
197             for (int ct = 0; ct < 200; ct++) {
198                 boolean foundAllZeros = true;
199                 for (List<String> i : parseNetlink()) {
200                     // Column 5 is the "Rmem" field, which is the
201                     // amount of kernel memory for received netlink messages.
202                     if (!i.get(4).equals("0")) {
203                         foundAllZeros = false;
204                     }
205                 }
206                 if (foundAllZeros) {
207                     return;
208                 }
209                 Thread.sleep(50);
210             }
211         } catch (InterruptedException e) {
212             throw new RuntimeException(e);
213         }
214     }
215 
216     /**
217      * Extract all the PIDs listening for netlink messages.
218      */
getPids()219     private static Set<Integer> getPids() {
220         List<List<String>> netlink = parseNetlink();
221         Set<Integer> retval = new HashSet<Integer>();
222         for (List<String> i : netlink) {
223             // The PID is in column 3
224             int pid = Long.decode(i.get(2)).intValue();
225             if (new File("/proc/" + pid + "/cmdline").exists()) {
226                 retval.add(pid);
227             }
228         }
229         return retval;
230     }
231 
232     /**
233      * Parse /proc/net/netlink and return a List of lines
234      * (excluding the first line)
235      */
parseNetlink()236     private static List<List<String>> parseNetlink() {
237         List<List<String>> retval = new ArrayList<List<String>>();
238         File netlink = new File("/proc/net/netlink");
239         Scanner scanner = null;
240         try {
241             scanner = new Scanner(netlink);
242             while(scanner.hasNextLine()) {
243                 String line = scanner.nextLine().trim();
244                 if (line.startsWith("sk")) {
245                     continue;
246                 }
247 
248                 List<String> lineList = Arrays.asList(line.split("\\s+"));
249                 retval.add(lineList);
250             }
251         } catch (IOException e) {
252             throw new RuntimeException(e);
253         } finally {
254             if (scanner != null) {
255                 scanner.close();
256             }
257         }
258         return retval;
259     }
260 
getDiskAddedMessage(String path)261     private static byte[] getDiskAddedMessage(String path) {
262         try {
263             return ("@/foo\0ACTION=add\0SUBSYSTEM=block\0"
264                     + "DEVPATH=" + path + "\0MAJOR=179\0MINOR=12345"
265                     + "\0DEVTYPE=disk\0").getBytes("ASCII");
266         } catch (UnsupportedEncodingException e) {
267             throw new RuntimeException(e);
268         }
269     }
270 
getPartitionAddedMessage( String path, int partitionNum)271     private static byte[] getPartitionAddedMessage(
272             String path, int partitionNum) {
273         try {
274             return ("@/foo\0ACTION=add\0SUBSYSTEM=block\0"
275                     + "DEVPATH=" + path + "\0MAJOR=179\0MINOR=12345"
276                     + "\0DEVTYPE=blah\0PARTN=" + partitionNum + "\0")
277                     .getBytes("ASCII");
278         } catch (UnsupportedEncodingException e) {
279             throw new RuntimeException(e);
280         }
281     }
282 }
283