1 package android.security; 2 3 import android.system.Os; 4 import android.test.AndroidTestCase; 5 import java.io.BufferedReader; 6 import java.io.FileReader; 7 import java.io.IOException; 8 import java.util.Scanner; 9 import java.io.File; 10 import java.io.IOException; 11 import java.net.NetworkInterface; 12 import java.util.Collections; 13 import java.util.regex.Pattern; 14 import java.util.regex.Matcher; 15 import org.junit.Assert; 16 17 abstract class SELinuxTargetSdkTestBase extends AndroidTestCase 18 { 19 static { 20 System.loadLibrary("ctsselinux_jni"); 21 } 22 23 static final byte[] ANONYMIZED_HARDWARE_ADDRESS = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }; 24 getFile(String filename)25 protected static String getFile(String filename) throws IOException { 26 BufferedReader in = null; 27 try { 28 in = new BufferedReader(new FileReader(filename)); 29 return in.readLine().trim(); 30 } finally { 31 if (in != null) { 32 in.close(); 33 } 34 } 35 } 36 getProperty(String property)37 protected static String getProperty(String property) 38 throws IOException { 39 Process process = new ProcessBuilder("getprop", property).start(); 40 Scanner scanner = null; 41 String line = ""; 42 try { 43 scanner = new Scanner(process.getInputStream()); 44 line = scanner.nextLine(); 45 } finally { 46 if (scanner != null) { 47 scanner.close(); 48 } 49 } 50 return line; 51 } 52 53 /** 54 * Verify that net.dns properties may not be read 55 */ noDns()56 protected static void noDns() throws IOException { 57 String[] dnsProps = {"net.dns1", "net.dns2", "net.dns3", "net.dns4"}; 58 for(int i = 0; i < dnsProps.length; i++) { 59 String dns = getProperty(dnsProps[i]); 60 assertEquals("DNS properties may not be readable by apps past " + 61 "targetSdkVersion 26", "", dns); 62 } 63 } 64 noNetlinkRouteGetlink()65 protected static void noNetlinkRouteGetlink() throws IOException { 66 assertEquals( 67 "RTM_GETLINK is not allowed on netlink route sockets. Verify that the" 68 + " following patch has been applied to your kernel: " 69 + "https://android-review.googlesource.com/q/I7b44ce60ad98f858c412722d41b9842f8577151f", 70 13, 71 checkNetlinkRouteGetlink()); 72 } 73 checkNetlinkRouteGetneigh(boolean expectAllowed)74 protected static void checkNetlinkRouteGetneigh(boolean expectAllowed) throws IOException { 75 if (!expectAllowed) { 76 assertEquals( 77 "RTM_GETNEIGH is not allowed on netlink route sockets. Verify that the" 78 + " following patch has been applied to your kernel: " 79 + "https://r.android.com/1690896", 80 3, 81 checkNetlinkRouteGetneigh()); 82 } else { 83 assertEquals( 84 "RTM_GETNEIGH should be allowed on netlink route sockets for apps with " 85 + "targetSdkVersion <= S", 86 0, 87 checkNetlinkRouteGetneigh()); 88 } 89 } 90 noNetlinkRouteBind()91 protected static void noNetlinkRouteBind() throws IOException { 92 assertEquals( 93 "bind() is not allowed on netlink route sockets", 94 13, 95 checkNetlinkRouteBind()); 96 } 97 98 /** 99 * Check expectations of being able to read/execute dex2oat. 100 */ checkDex2oatAccess(boolean expectedAllowed)101 protected static void checkDex2oatAccess(boolean expectedAllowed) throws Exception { 102 // Check the dex2oat binary in its current and legacy locations. 103 String[] locations = {"/apex/com.android.art/bin", 104 "/apex/com.android.runtime/bin", 105 "/system/bin"}; 106 for (String loc : locations) { 107 File dex2oatBinary = new File(loc + "/dex2oat"); 108 if (dex2oatBinary.exists()) { 109 checkDex2oatBinaryAccess(dex2oatBinary, expectedAllowed); 110 } 111 } 112 } 113 checkDex2oatBinaryAccess(File dex2oatBinary, boolean expectedAllowed)114 private static void checkDex2oatBinaryAccess(File dex2oatBinary, boolean expectedAllowed) 115 throws Exception { 116 // Check permissions. 117 assertEquals(expectedAllowed, dex2oatBinary.canRead()); 118 assertEquals(expectedAllowed, dex2oatBinary.canExecute()); 119 120 // Try to execute dex2oat. 121 try { 122 Runtime rt = Runtime.getRuntime(); 123 Process p = rt.exec(dex2oatBinary.getAbsolutePath()); 124 p.waitFor(); 125 assertEquals(expectedAllowed, true); 126 } catch (IOException ex) { 127 assertEquals(expectedAllowed, false); 128 assertEquals(ex.getMessage(), 129 "Cannot run program \"" + dex2oatBinary.getAbsolutePath() + 130 "\": error=13, Permission denied"); 131 } 132 } 133 134 /** 135 * Verify that selinux context is the expected domain based on 136 * targetSdkVersion, 137 */ appDomainContext(String contextRegex, String errorMsg)138 protected void appDomainContext(String contextRegex, String errorMsg) throws IOException { 139 Pattern p = Pattern.compile(contextRegex); 140 Matcher m = p.matcher(getFile("/proc/self/attr/current")); 141 String context = getFile("/proc/self/attr/current"); 142 String msg = errorMsg + context; 143 assertTrue(msg, m.matches()); 144 } 145 146 /** 147 * Verify that selinux context is the expected type based on 148 * targetSdkVersion, 149 */ appDataContext(String contextRegex, String errorMsg)150 protected void appDataContext(String contextRegex, String errorMsg) throws Exception { 151 Pattern p = Pattern.compile(contextRegex); 152 File appDataDir = getContext().getFilesDir(); 153 Matcher m = p.matcher(getFileContext(appDataDir.getAbsolutePath())); 154 String context = getFileContext(appDataDir.getAbsolutePath()); 155 String msg = errorMsg + context; 156 assertTrue(msg, m.matches()); 157 } 158 canExecuteFromHomeDir()159 protected boolean canExecuteFromHomeDir() throws Exception { 160 File appDataDir = getContext().getFilesDir(); 161 File temp = File.createTempFile("badbin", "exe", appDataDir); 162 temp.deleteOnExit(); 163 String path = temp.getPath(); 164 Os.chmod(path, 0700); 165 try { 166 Process process = new ProcessBuilder(path).start(); 167 } catch (IOException e) { 168 return !e.toString().contains("Permission denied"); 169 } finally { 170 temp.delete(); 171 } 172 return true; 173 } 174 175 /** 176 * Verify that apps are not able to see MAC addresses of ethernet devices. 177 */ checkNetworkInterfaceHardwareAddress_returnsNull()178 protected static void checkNetworkInterfaceHardwareAddress_returnsNull() throws Exception { 179 assertNotNull(NetworkInterface.getNetworkInterfaces()); 180 for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) { 181 assertNull(nif.getHardwareAddress()); 182 } 183 } 184 185 /** 186 * Verify that apps having targetSdkVersion <= 29 get an anonymized MAC 187 * address (02:00:00:00:00:00) instead of a null MAC for ethernet interfaces. 188 */ checkNetworkInterface_returnsAnonymizedHardwareAddresses()189 protected static void checkNetworkInterface_returnsAnonymizedHardwareAddresses() 190 throws Exception { 191 assertNotNull(NetworkInterface.getNetworkInterfaces()); 192 for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) { 193 if (isEthernet(nif.getName())) { 194 Assert.assertArrayEquals(ANONYMIZED_HARDWARE_ADDRESS, nif.getHardwareAddress()); 195 } 196 } 197 } 198 199 /** 200 * Verify that hidden ro properties are not readable. 201 */ noHiddenSystemProperties()202 protected static void noHiddenSystemProperties() throws IOException { 203 String[] hiddenProperties = {"ro.debuggable", "ro.secure"}; 204 for(int i = 0; i < hiddenProperties.length; i++) { 205 String roHidden = getProperty(hiddenProperties[i]); 206 assertEquals("Hidden system properties may not be readable by apps", "", roHidden); 207 } 208 } 209 210 /** 211 * Checks whether a network interface is an ethernet interface. 212 */ 213 private static Pattern ethernetNamePattern = Pattern.compile("^(eth|wlan)[0-9]+$"); isEthernet(String ifName)214 private static boolean isEthernet(String ifName) throws Exception { 215 return ethernetNamePattern.matcher(ifName).matches(); 216 } 217 checkNetlinkRouteGetlink()218 private static final native int checkNetlinkRouteGetlink(); checkNetlinkRouteGetneigh()219 private static final native int checkNetlinkRouteGetneigh(); checkNetlinkRouteBind()220 private static final native int checkNetlinkRouteBind(); getFileContext(String path)221 private static final native String getFileContext(String path); 222 } 223