blob: b7d90d6cded5a9cc49ccb31a6cb5706a16ff6970 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.appsecurity.cts;
import com.android.tradefed.util.RunUtil;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* Runs the n portion of the listening ports test. With /proc/net access removed for third
* party apps the device side test could no longer directly parse the file for listening ports. This
* class pulls the /proc/net files from the device and passes their contents as a parameter to the
* device side test that will then check for any listening ports and perform any necessary
* localhost connection tests.
*/
public class ListeningPortsTest extends DeviceTestCase implements IBuildReceiver {
private static final String DEVICE_TEST_APK = "CtsListeningPortsTest.apk";
private static final String DEVICE_TEST_PKG = "android.appsecurity.cts.listeningports";
private static final String DEVICE_TEST_CLASS = DEVICE_TEST_PKG + ".ListeningPortsTest";
private static final String DEVICE_TEST_METHOD = "testNoAccessibleListeningPorts";
private static final String PROC_FILE_CONTENTS_PARAM = "procFileContents";
private static final String IS_TCP_PARAM = "isTcp";
private static final String LOOPBACK_PARAM = "loopback";
private IBuildInfo mCtsBuild;
@Override
public void setBuild(IBuildInfo buildInfo) {
mCtsBuild = buildInfo;
}
@Override
protected void setUp() throws Exception {
super.setUp();
Utils.prepareSingleUser(getDevice());
assertNotNull(mCtsBuild);
installDeviceTestPkg();
}
@Override
protected void tearDown() throws Exception {
try {
uninstallDeviceTestPackage();
} catch (DeviceNotAvailableException ignored) {
} finally {
super.tearDown();
}
}
private void installDeviceTestPkg() throws Exception {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
File apk = buildHelper.getTestFile(DEVICE_TEST_APK);
String result = getDevice().installPackage(apk, true);
assertNull("failed to install " + DEVICE_TEST_APK + ", Reason: " + result, result);
}
private String uninstallDeviceTestPackage() throws DeviceNotAvailableException {
return getDevice().uninstallPackage(DEVICE_TEST_PKG);
}
/**
* Remotely accessible ports are often used by attackers to gain
* unauthorized access to computers systems without user knowledge or
* awareness.
*/
public void testNoRemotelyAccessibleListeningTcpPorts() throws Exception {
assertNoAccessibleListeningPorts("/proc/net/tcp", true, false);
}
/**
* Remotely accessible ports are often used by attackers to gain
* unauthorized access to computers systems without user knowledge or
* awareness.
*/
public void testNoRemotelyAccessibleListeningTcp6Ports() throws Exception {
assertNoAccessibleListeningPorts("/proc/net/tcp6", true, false);
}
/**
* Remotely accessible ports are often used by attackers to gain
* unauthorized access to computers systems without user knowledge or
* awareness.
*/
public void testNoRemotelyAccessibleListeningUdpPorts() throws Exception {
assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp", false);
}
/**
* Remotely accessible ports are often used by attackers to gain
* unauthorized access to computers systems without user knowledge or
* awareness.
*/
/*
public void testNoRemotelyAccessibleListeningUdp6Ports() throws Exception {
assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp6", false);
}
*/
/**
* Locally accessible ports are often targeted by malicious locally
* installed programs to gain unauthorized access to program data or
* cause system corruption.
*
* In all cases, a local listening IP port can be replaced by a UNIX domain
* socket. Unix domain sockets can be protected with unix filesystem
* permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
* determine if a program is authorized to connect to your socket.
*
* Please convert loopback IP connections to unix domain sockets.
*/
public void testNoListeningLoopbackTcpPorts() throws Exception {
assertNoAccessibleListeningPorts("/proc/net/tcp", true, true);
}
/**
* Locally accessible ports are often targeted by malicious locally
* installed programs to gain unauthorized access to program data or
* cause system corruption.
*
* In all cases, a local listening IP port can be replaced by a UNIX domain
* socket. Unix domain sockets can be protected with unix filesystem
* permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
* determine if a program is authorized to connect to your socket.
*
* Please convert loopback IP connections to unix domain sockets.
*/
public void testNoListeningLoopbackTcp6Ports() throws Exception {
assertNoAccessibleListeningPorts("/proc/net/tcp6", true, true);
}
/**
* Locally accessible ports are often targeted by malicious locally
* installed programs to gain unauthorized access to program data or
* cause system corruption.
*
* In all cases, a local listening IP port can be replaced by a UNIX domain
* socket. Unix domain sockets can be protected with unix filesystem
* permission. Alternately, or you can use setsockopt(SO_PASSCRED) to
* send credentials, and recvmsg to retrieve the passed credentials.
*
* Please convert loopback IP connections to unix domain sockets.
*/
public void testNoListeningLoopbackUdpPorts() throws Exception {
assertNoAccessibleListeningPorts("/proc/net/udp", false, true);
}
/**
* Locally accessible ports are often targeted by malicious locally
* installed programs to gain unauthorized access to program data or
* cause system corruption.
*
* In all cases, a local listening IP port can be replaced by a UNIX domain
* socket. Unix domain sockets can be protected with unix filesystem
* permission. Alternately, or you can use setsockopt(SO_PASSCRED) to
* send credentials, and recvmsg to retrieve the passed credentials.
*
* Please convert loopback IP connections to unix domain sockets.
*/
public void testNoListeningLoopbackUdp6Ports() throws Exception {
assertNoAccessibleListeningPorts("/proc/net/udp6", false, true);
}
private static final int RETRIES_MAX = 6;
/**
* UDP tests can be flaky due to DNS lookups. Compensate.
*/
private void assertNoRemotelyAccessibleListeningUdpPorts(String procFilePath, boolean loopback)
throws Exception {
for (int i = 0; i < RETRIES_MAX; i++) {
try {
assertNoAccessibleListeningPorts(procFilePath, false, loopback);
return;
} catch (AssertionError e) {
// If an AssertionError is caught then a listening UDP port was detected on the
// device; since this could be due to a DNS lookup sleep and retry the test. If
// the test continues to fail then report the error.
if (i == RETRIES_MAX - 1) {
throw e;
}
RunUtil.getDefault().sleep(2 * 1000 * i);
}
}
throw new IllegalStateException("unreachable");
}
/**
* Passes the contents of the /proc/net file and the boolean arguments to the device side test
* which performs the necessary checks to determine if the port is listening and if it is
* accessible from the localhost.
*/
private void assertNoAccessibleListeningPorts(String procFilePath, boolean isTcp,
boolean loopback) throws Exception {
Map<String, String> args = new HashMap<>();
String procFileContents = parse(procFilePath);
args.put(PROC_FILE_CONTENTS_PARAM, procFileContents);
args.put(IS_TCP_PARAM, String.valueOf(isTcp));
args.put(LOOPBACK_PARAM, String.valueOf(loopback));
Utils.runDeviceTests(getDevice(), DEVICE_TEST_PKG, DEVICE_TEST_CLASS, DEVICE_TEST_METHOD,
args);
}
/**
* Since the device side test cannot directly access the /proc/net files this method pulls the
* file from the device and returns a String with its contents which can then be passed as an
* argument to the device side test.
*/
private String parse(String procFilePath) throws IOException, DeviceNotAvailableException {
File procFile = File.createTempFile("ListeningPortsTest", "tmp");
boolean result = getDevice().pullFile(procFilePath, procFile);
assertTrue("failed to pull " + procFilePath, result);
procFile.deleteOnExit();
Scanner scanner = null;
StringBuilder contents = new StringBuilder();
// When passing the file contents as an argument to the device side method a 'No
// instrumentation found' error was reported due to spaces in the String. To prevent this
// the entire contents of the file need to be quoted before passing it as an argument.
contents.append("'");
try {
scanner = new Scanner(procFile);
while (scanner.hasNextLine()) {
contents.append(scanner.nextLine() + "\n");
}
} finally {
if (scanner != null) {
scanner.close();
}
}
contents.append("'");
return contents.toString();
}
}