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