blob: 76c53c1417564e55f03faf18fccf376a229887e3 [file] [log] [blame]
Nick Kralevich8ed56012011-11-01 16:53:21 -07001/*
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
17package android.security.cts;
18
Nick Kralevichb830c402013-10-04 15:21:37 -070019import android.content.pm.PackageManager;
20import android.test.AndroidTestCase;
Nick Kralevich8ed56012011-11-01 16:53:21 -070021import junit.framework.AssertionFailedError;
Nick Kralevich8ed56012011-11-01 16:53:21 -070022
23import java.io.File;
24import java.io.IOException;
25import java.net.InetAddress;
26import java.net.UnknownHostException;
27import java.util.ArrayList;
Nick Kralevichb830c402013-10-04 15:21:37 -070028import java.util.Arrays;
Nick Kralevich8ed56012011-11-01 16:53:21 -070029import java.util.List;
30import java.util.Scanner;
31import java.util.regex.Pattern;
32
33/**
34 * Verifies that Android devices are not listening on accessible
35 * open ports. Open ports are often targeted by attackers looking to break
36 * into computer systems remotely, and minimizing the number of open ports
37 * is considered a security best practice.
38 */
Nick Kralevichb830c402013-10-04 15:21:37 -070039public class ListeningPortsTest extends AndroidTestCase {
Nick Kralevich8ed56012011-11-01 16:53:21 -070040
41 /** Ports that are allowed to be listening. */
42 private static final List<String> EXCEPTION_PATTERNS = new ArrayList<String>(6);
43
44 static {
45 // IPv4 exceptions
Christopher3ece5ae2015-09-30 15:50:14 -070046 // Patterns containing ":" are allowed address port combinations
47 // Pattterns contains " " are allowed address UID combinations
48 // Patterns containing both are allowed address, port, and UID combinations
49 EXCEPTION_PATTERNS.add("0.0.0.0:5555"); // emulator port
50 EXCEPTION_PATTERNS.add("0.0.0.0:9101"); // verified ports
51 EXCEPTION_PATTERNS.add("0.0.0.0:9551"); // verified ports
52 EXCEPTION_PATTERNS.add("0.0.0.0:9552"); // verified ports
53 EXCEPTION_PATTERNS.add("10.0.2.15:5555"); // net forwarding for emulator
54 EXCEPTION_PATTERNS.add("127.0.0.1:5037"); // adb daemon "smart sockets"
55 EXCEPTION_PATTERNS.add("0.0.0.0 1020"); // used by the cast receiver
56 EXCEPTION_PATTERNS.add("0.0.0.0 10000"); // used by the cast receiver
57 EXCEPTION_PATTERNS.add("127.0.0.1 10000"); // used by the cast receiver
58 EXCEPTION_PATTERNS.add(":: 1002"); // used by remote control
59 EXCEPTION_PATTERNS.add(":: 1020"); // used by remote control
60 //no current patterns involve address, port and UID combinations
61 //Example for when necessary: EXCEPTION_PATTERNS.add("0.0.0.0:5555 10000")
Nick Kralevich8ed56012011-11-01 16:53:21 -070062 }
63
64 /**
65 * Remotely accessible ports are often used by attackers to gain
66 * unauthorized access to computers systems without user knowledge or
67 * awareness.
68 */
69 public void testNoRemotelyAccessibleListeningTcpPorts() throws Exception {
70 assertNoAccessibleListeningPorts("/proc/net/tcp", true, false);
71 }
72
73 /**
74 * Remotely accessible ports are often used by attackers to gain
75 * unauthorized access to computers systems without user knowledge or
76 * awareness.
77 */
78 public void testNoRemotelyAccessibleListeningTcp6Ports() throws Exception {
79 assertNoAccessibleListeningPorts("/proc/net/tcp6", true, false);
80 }
81
82 /**
83 * Remotely accessible ports are often used by attackers to gain
84 * unauthorized access to computers systems without user knowledge or
85 * awareness.
86 */
87 public void testNoRemotelyAccessibleListeningUdpPorts() throws Exception {
88 assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp", false);
89 }
90
91 /**
92 * Remotely accessible ports are often used by attackers to gain
93 * unauthorized access to computers systems without user knowledge or
94 * awareness.
95 */
96 public void testNoRemotelyAccessibleListeningUdp6Ports() throws Exception {
97 assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp6", false);
98 }
99
100 /**
101 * Locally accessible ports are often targeted by malicious locally
102 * installed programs to gain unauthorized access to program data or
103 * cause system corruption.
104 *
105 * In all cases, a local listening IP port can be replaced by a UNIX domain
106 * socket. Unix domain sockets can be protected with unix filesystem
107 * permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
108 * determine if a program is authorized to connect to your socket.
109 *
110 * Please convert loopback IP connections to unix domain sockets.
111 */
112 public void testNoListeningLoopbackTcpPorts() throws Exception {
113 assertNoAccessibleListeningPorts("/proc/net/tcp", true, true);
114 }
115
116 /**
117 * Locally accessible ports are often targeted by malicious locally
118 * installed programs to gain unauthorized access to program data or
119 * cause system corruption.
120 *
121 * In all cases, a local listening IP port can be replaced by a UNIX domain
122 * socket. Unix domain sockets can be protected with unix filesystem
123 * permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
124 * determine if a program is authorized to connect to your socket.
125 *
126 * Please convert loopback IP connections to unix domain sockets.
127 */
128 public void testNoListeningLoopbackTcp6Ports() throws Exception {
129 assertNoAccessibleListeningPorts("/proc/net/tcp6", true, true);
130 }
131
132 /**
133 * Locally accessible ports are often targeted by malicious locally
134 * installed programs to gain unauthorized access to program data or
135 * cause system corruption.
136 *
137 * In all cases, a local listening IP port can be replaced by a UNIX domain
138 * socket. Unix domain sockets can be protected with unix filesystem
139 * permission. Alternately, or you can use setsockopt(SO_PASSCRED) to
140 * send credentials, and recvmsg to retrieve the passed credentials.
141 *
142 * Please convert loopback IP connections to unix domain sockets.
143 */
144 public void testNoListeningLoopbackUdpPorts() throws Exception {
145 assertNoAccessibleListeningPorts("/proc/net/udp", false, true);
146 }
147
148 /**
149 * Locally accessible ports are often targeted by malicious locally
150 * installed programs to gain unauthorized access to program data or
151 * cause system corruption.
152 *
153 * In all cases, a local listening IP port can be replaced by a UNIX domain
154 * socket. Unix domain sockets can be protected with unix filesystem
155 * permission. Alternately, or you can use setsockopt(SO_PASSCRED) to
156 * send credentials, and recvmsg to retrieve the passed credentials.
157 *
158 * Please convert loopback IP connections to unix domain sockets.
159 */
160 public void testNoListeningLoopbackUdp6Ports() throws Exception {
161 assertNoAccessibleListeningPorts("/proc/net/udp6", false, true);
162 }
163
164 private static final int RETRIES_MAX = 6;
165
166 /**
167 * UDP tests can be flaky due to DNS lookups. Compensate.
168 */
Nick Kralevichb830c402013-10-04 15:21:37 -0700169 private void assertNoRemotelyAccessibleListeningUdpPorts(
Nick Kralevich8ed56012011-11-01 16:53:21 -0700170 String procFilePath, boolean loopback)
171 throws Exception {
172 for (int i = 0; i < RETRIES_MAX; i++) {
173 try {
174 assertNoAccessibleListeningPorts(procFilePath, false, loopback);
175 return;
176 } catch (ListeningPortsAssertionError e) {
177 if (i == RETRIES_MAX - 1) {
178 throw e;
179 }
180 Thread.sleep(2 * 1000 * i);
181 }
182 }
183 throw new IllegalStateException("unreachable");
184 }
185
186 /**
187 * Remotely accessible ports (loopback==false) are often used by
188 * attackers to gain unauthorized access to computers systems without
189 * user knowledge or awareness.
190 *
191 * Locally accessible ports (loopback==true) are often targeted by
192 * malicious locally installed programs to gain unauthorized access to
193 * program data or cause system corruption.
194 */
Nick Kralevichb830c402013-10-04 15:21:37 -0700195 private void assertNoAccessibleListeningPorts(
Nick Kralevich8ed56012011-11-01 16:53:21 -0700196 String procFilePath, boolean isTcp, boolean loopback) throws IOException {
197 String errors = "";
198 List<ParsedProcEntry> entries = ParsedProcEntry.parse(procFilePath);
199 for (ParsedProcEntry entry : entries) {
200 String addrPort = entry.localAddress.getHostAddress() + ':' + entry.port;
Christopher3ece5ae2015-09-30 15:50:14 -0700201 String addrUid = entry.localAddress.getHostAddress() + ' ' + entry.uid;
202 String addrPortUid = addrPort + ' ' + entry.uid;
Nick Kralevich8ed56012011-11-01 16:53:21 -0700203
204 if (isPortListening(entry.state, isTcp)
Christopher3ece5ae2015-09-30 15:50:14 -0700205 && !(isException(addrPort) || isException(addrUid) || isException(addrPortUid))
Nick Kralevich8ed56012011-11-01 16:53:21 -0700206 && (!entry.localAddress.isLoopbackAddress() ^ loopback)) {
207 errors += "\nFound port listening on addr="
208 + entry.localAddress.getHostAddress() + ", port="
Nick Kralevichb830c402013-10-04 15:21:37 -0700209 + entry.port + ", UID=" + entry.uid
210 + " " + uidToPackage(entry.uid) + " in "
Nick Kralevich8ed56012011-11-01 16:53:21 -0700211 + procFilePath;
212 }
213 }
214 if (!errors.equals("")) {
Nick Kralevich3a25ca52013-10-17 09:28:13 -0700215 throw new ListeningPortsAssertionError(errors);
Nick Kralevich8ed56012011-11-01 16:53:21 -0700216 }
217 }
218
Nick Kralevichb830c402013-10-04 15:21:37 -0700219 private String uidToPackage(int uid) {
220 PackageManager pm = this.getContext().getPackageManager();
221 String[] packages = pm.getPackagesForUid(uid);
222 if (packages == null) {
223 return "[unknown]";
224 }
225 return Arrays.asList(packages).toString();
226 }
227
Nick Kralevich8ed56012011-11-01 16:53:21 -0700228 private static boolean isException(String localAddress) {
229 return isPatternMatch(EXCEPTION_PATTERNS, localAddress);
230 }
231
232 private static boolean isPatternMatch(List<String> patterns, String input) {
233 for (String pattern : patterns) {
234 pattern = Pattern.quote(pattern);
235 if (Pattern.matches(pattern, input)) {
236 return true;
237 }
238 }
239 return false;
240 }
241
242 private static boolean isPortListening(String state, boolean isTcp) {
243 // 0A = TCP_LISTEN from include/net/tcp_states.h
244 String listeningState = isTcp ? "0A" : "07";
245 return listeningState.equals(state);
246 }
247
248 private static class ListeningPortsAssertionError extends AssertionFailedError {
249 private ListeningPortsAssertionError(String msg) {
250 super(msg);
251 }
252 }
253
254 private static class ParsedProcEntry {
255 private final InetAddress localAddress;
256 private final int port;
257 private final String state;
258 private final int uid;
259
260 private ParsedProcEntry(InetAddress addr, int port, String state, int uid) {
261 this.localAddress = addr;
262 this.port = port;
263 this.state = state;
264 this.uid = uid;
265 }
266
267
268 private static List<ParsedProcEntry> parse(String procFilePath) throws IOException {
269
270 List<ParsedProcEntry> retval = new ArrayList<ParsedProcEntry>();
271 /*
272 * Sample output of "cat /proc/net/tcp" on emulator:
273 *
274 * sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid ...
275 * 0: 0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 ...
276 * 1: 00000000:15B3 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 ...
277 * 2: 0F02000A:15B3 0202000A:CE8A 01 00000000:00000000 00:00000000 00000000 0 ...
278 *
279 */
280
281 File procFile = new File(procFilePath);
282 Scanner scanner = null;
283 try {
284 scanner = new Scanner(procFile);
285 while (scanner.hasNextLine()) {
286 String line = scanner.nextLine().trim();
287
288 // Skip column headers
289 if (line.startsWith("sl")) {
290 continue;
291 }
292
293 String[] fields = line.split("\\s+");
294 final int expectedNumColumns = 12;
295 assertTrue(procFilePath + " should have at least " + expectedNumColumns
296 + " columns of output " + fields, fields.length >= expectedNumColumns);
297
298 String state = fields[3];
299 int uid = Integer.parseInt(fields[7]);
300 InetAddress localIp = addrToInet(fields[1].split(":")[0]);
301 int localPort = Integer.parseInt(fields[1].split(":")[1], 16);
302
303 retval.add(new ParsedProcEntry(localIp, localPort, state, uid));
304 }
305 } finally {
306 if (scanner != null) {
307 scanner.close();
308 }
309 }
310 return retval;
311 }
312
313 /**
314 * Convert a string stored in little endian format to an IP address.
315 */
316 private static InetAddress addrToInet(String s) throws UnknownHostException {
317 int len = s.length();
318 if (len != 8 && len != 32) {
319 throw new IllegalArgumentException(len + "");
320 }
321 byte[] retval = new byte[len / 2];
322
323 for (int i = 0; i < len / 2; i += 4) {
324 retval[i] = (byte) ((Character.digit(s.charAt(2*i + 6), 16) << 4)
325 + Character.digit(s.charAt(2*i + 7), 16));
326 retval[i + 1] = (byte) ((Character.digit(s.charAt(2*i + 4), 16) << 4)
327 + Character.digit(s.charAt(2*i + 5), 16));
328 retval[i + 2] = (byte) ((Character.digit(s.charAt(2*i + 2), 16) << 4)
329 + Character.digit(s.charAt(2*i + 3), 16));
330 retval[i + 3] = (byte) ((Character.digit(s.charAt(2*i), 16) << 4)
331 + Character.digit(s.charAt(2*i + 1), 16));
332 }
333 return InetAddress.getByAddress(retval);
334 }
335 }
336}