1 /* 2 * Copyright (C) 2022 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 package android.security.cts; 17 18 import static org.junit.Assert.assertTrue; 19 20 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 23 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 24 25 import org.junit.Before; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 29 import java.io.BufferedReader; 30 import java.io.File; 31 import java.io.FileOutputStream; 32 import java.io.FileReader; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.util.Arrays; 37 import java.util.List; 38 import java.util.Objects; 39 import java.util.stream.Stream; 40 41 @RunWith(DeviceJUnit4ClassRunner.class) 42 public class SeamendcHostTest extends BaseHostJUnit4Test { 43 44 private ITestDevice mDevice; 45 46 // executable binaries 47 private File seamendc; 48 private File secilc; 49 private File searchpolicy; 50 private File libsepolwrap; 51 52 // CIL policies 53 private File mPlatPolicyCil; 54 private File mPlatCompatCil; 55 private File mSystemExtPolicyCil; 56 private File mSystemExtMappingCil; 57 private File mSystemExtCompatCil; 58 private File mProductPolicyCil; 59 private File mProductMappingCil; 60 private File mVendorPolicyCil; 61 private File mPlatPubVersionedCil; 62 private File mOdmPolicyCil; 63 private File mApexSepolicyCil; 64 private File mApexSepolicyDecompiledCil; 65 66 @Before setUp()67 public void setUp() throws Exception { 68 mDevice = getDevice(); 69 70 seamendc = copyResToTempFile("/seamendc"); 71 seamendc.setExecutable(true); 72 secilc = copyResToTempFile("/secilc"); 73 secilc.setExecutable(true); 74 searchpolicy = copyResToTempFile("/searchpolicy"); 75 searchpolicy.setExecutable(true); 76 libsepolwrap = new CompatibilityBuildHelper(getBuild()).getTestFile("libsepolwrap.so"); 77 libsepolwrap.deleteOnExit(); 78 79 // Pull CIL files for policy compilation, using selinux.cpp as reference 80 // https://cs.android.com/android/platform/superproject/+/master:system/core/init/selinux.cpp;l=378-453;drc=2d579af880afae96239c80765b11c7dbc2c1b264 81 mPlatPolicyCil = getPlatPolicyFromDevice(); 82 String vendorMappingVersion = 83 readFirstLine("/vendor/etc/selinux/", "plat_sepolicy_vers.txt"); 84 mPlatCompatCil = 85 getDeviceFile("/system/etc/selinux/mapping/", vendorMappingVersion + ".cil"); 86 mSystemExtPolicyCil = getDeviceFile("/system_ext/etc/selinux/", "system_ext_sepolicy.cil"); 87 mSystemExtMappingCil = 88 getDeviceFile("/system_ext/etc/selinux/mapping/", vendorMappingVersion + ".cil"); 89 mSystemExtCompatCil = 90 getDeviceFile( 91 "/system_ext/etc/selinux/mapping/", vendorMappingVersion + ".compat.cil"); 92 mProductPolicyCil = getDeviceFile("/product/etc/selinux/", "product_sepolicy.cil"); 93 mProductMappingCil = 94 getDeviceFile("/product/etc/selinux/mapping", vendorMappingVersion + ".cil"); 95 mVendorPolicyCil = getDeviceFile("/vendor/etc/selinux/", "vendor_sepolicy.cil"); 96 mPlatPubVersionedCil = getDeviceFile("/vendor/etc/selinux/", "plat_pub_versioned.cil"); 97 mOdmPolicyCil = getDeviceFile("/odm/etc/selinux/", "odm_sepolicy.cil"); 98 mApexSepolicyCil = copyResToTempFile("/apex_sepolicy.cil"); 99 100 mApexSepolicyDecompiledCil = copyResToTempFile("/apex_sepolicy.decompiled.cil"); 101 } 102 103 /** 104 * Verifies that the files necessary for policy compilation exist. 105 * 106 * @throws Exception 107 */ 108 @Test testRequiredDeviceFilesArePresent()109 public void testRequiredDeviceFilesArePresent() throws Exception { 110 assertTrue(mPlatPolicyCil.getName() + " is missing", mPlatPolicyCil != null); 111 assertTrue(mPlatCompatCil.getName() + " is missing", mPlatCompatCil != null); 112 assertTrue(mVendorPolicyCil.getName() + " is missing", mVendorPolicyCil != null); 113 assertTrue(mPlatPubVersionedCil.getName() + " is missing", mPlatPubVersionedCil != null); 114 } 115 getPlatPolicyFromDevice()116 private File getPlatPolicyFromDevice() throws Exception { 117 File policyFile = getDeviceFile("/system_ext/etc/selinux/", "userdebug_plat_sepolicy.cil"); 118 if (policyFile == null) { 119 policyFile = getDeviceFile("/debug_ramdisk/", "userdebug_plat_sepolicy.cil"); 120 } 121 if (policyFile == null) { 122 policyFile = getDeviceFile("/system/etc/selinux/", "plat_sepolicy.cil"); 123 } 124 return policyFile; 125 } 126 readFirstLine(String filePath, String fileName)127 private String readFirstLine(String filePath, String fileName) throws Exception { 128 try (BufferedReader brTest = 129 new BufferedReader(new FileReader(getDeviceFile(filePath, fileName)))) { 130 return brTest.readLine(); 131 } 132 } 133 getDeviceFile(String filePath, String fileName)134 private File getDeviceFile(String filePath, String fileName) throws Exception { 135 String deviceFile = filePath + fileName; 136 File file = File.createTempFile(fileName, ".tmp"); 137 file.deleteOnExit(); 138 return mDevice.pullFile(deviceFile, file) ? file : null; 139 } 140 copyResToTempFile(String resName)141 private static File copyResToTempFile(String resName) throws IOException { 142 InputStream is = SeamendcHostTest.class.getResourceAsStream(resName); 143 File tempFile = File.createTempFile(resName, ".tmp"); 144 FileOutputStream os = new FileOutputStream(tempFile); 145 byte[] buf = new byte[1024]; 146 int len; 147 while ((len = is.read(buf)) != -1) { 148 os.write(buf, 0, len); 149 } 150 os.flush(); 151 os.close(); 152 tempFile.deleteOnExit(); 153 return tempFile; 154 } 155 runProcess(String... args)156 private static String runProcess(String... args) throws Exception { 157 ProcessBuilder pb = new ProcessBuilder(args); 158 pb.redirectOutput(ProcessBuilder.Redirect.PIPE); 159 pb.redirectErrorStream(true); 160 Process p = pb.start(); 161 BufferedReader result = new BufferedReader(new InputStreamReader(p.getInputStream())); 162 String line; 163 StringBuilder errorString = new StringBuilder(); 164 while ((line = result.readLine()) != null) { 165 errorString.append(line); 166 errorString.append("\n"); 167 } 168 p.waitFor(); 169 return errorString.toString(); 170 } 171 searchpolicySource(File policy, String name)172 private String searchpolicySource(File policy, String name) throws Exception { 173 return runProcess( 174 searchpolicy.getAbsolutePath(), 175 "--allow", 176 "-s", 177 name, 178 "--libpath", 179 libsepolwrap.getAbsolutePath(), 180 policy.getAbsolutePath()); 181 } 182 searchpolicyTarget(File policy, String name)183 private String searchpolicyTarget(File policy, String name) throws Exception { 184 return runProcess( 185 searchpolicy.getAbsolutePath(), 186 "--allow", 187 "-t", 188 name, 189 "--libpath", 190 libsepolwrap.getAbsolutePath(), 191 policy.getAbsolutePath()); 192 } 193 194 /** Uses searchpolicy to verify the two policies wrt the provided source type. */ assertSource(File left, File right, String type, boolean equal)195 private void assertSource(File left, File right, String type, boolean equal) throws Exception { 196 String diff = diff(searchpolicySource(left, type), searchpolicySource(right, type)); 197 if (equal) { 198 assertTrue("Policy sources are not equal:\n" + diff, diff.length() == 0); 199 } else { 200 assertTrue("Policy sources should be different.", diff.length() != 0); 201 } 202 } 203 assertSourceEqual(File left, File right, String type)204 private void assertSourceEqual(File left, File right, String type) throws Exception { 205 assertSource(left, right, type, /* equal= */ true); 206 } 207 assertSourceNotEqual(File left, File right, String type)208 private void assertSourceNotEqual(File left, File right, String type) throws Exception { 209 assertSource(left, right, type, /* equal= */ false); 210 } 211 212 /** Uses searchpolicy to verify the two policies wrt the provided target type. */ assertTarget(File left, File right, String type, boolean equal)213 private void assertTarget(File left, File right, String type, boolean equal) throws Exception { 214 String diff = diff(searchpolicyTarget(left, type), searchpolicyTarget(right, type)); 215 if (equal) { 216 assertTrue("Policies are not equal:\n" + diff, diff.length() == 0); 217 } else { 218 assertTrue("Policies should be different.", diff.length() != 0); 219 } 220 } 221 assertTargetEqual(File left, File right, String type)222 private void assertTargetEqual(File left, File right, String type) throws Exception { 223 assertTarget(left, right, type, /* equal= */ true); 224 } 225 assertTargetNotEqual(File left, File right, String type)226 private void assertTargetNotEqual(File left, File right, String type) throws Exception { 227 assertTarget(left, right, type, /* equal= */ false); 228 } 229 runSeamendc(File basePolicy, File... cilFiles)230 private File runSeamendc(File basePolicy, File... cilFiles) throws Exception { 231 File output = File.createTempFile("seamendc-out", ".binary"); 232 output.deleteOnExit(); 233 String errorString = 234 runProcess( 235 Stream.concat( 236 Stream.of( 237 seamendc.getAbsolutePath(), 238 "-b", 239 basePolicy.getAbsolutePath(), 240 "-o", 241 output.getAbsolutePath()), 242 Stream.of(cilFiles).map(File::getAbsolutePath)) 243 .toArray(String[]::new)); 244 assertTrue(errorString, errorString.length() == 0); 245 return output; 246 } 247 runSecilc(File... cilFiles)248 private File runSecilc(File... cilFiles) throws Exception { 249 File fileContexts = File.createTempFile("file_contexts", ".txt"); 250 fileContexts.deleteOnExit(); 251 File output = File.createTempFile("secilc-out", ".binary"); 252 output.deleteOnExit(); 253 String errorString = 254 runProcess( 255 Stream.concat( 256 Stream.of( 257 secilc.getAbsolutePath(), 258 "-m", 259 "-M", 260 "true", 261 "-G", 262 "-N", 263 "-c", 264 "30", 265 "-f", 266 fileContexts.getAbsolutePath(), 267 "-o", 268 output.getAbsolutePath()), 269 Stream.of(cilFiles) 270 .filter(Objects::nonNull) 271 .map(File::getAbsolutePath)) 272 .toArray(String[]::new)); 273 assertTrue(errorString, errorString.length() == 0); 274 return output; 275 } 276 diff(String left, String right)277 private static String diff(String left, String right) { 278 List<String> leftLines = Arrays.asList(left.split("\\r?\\n")); 279 List<String> rightLines = Arrays.asList(right.split("\\r?\\n")); 280 281 // Generate diff information. 282 List<String> unifiedDiff = 283 difflib.DiffUtils.generateUnifiedDiff( 284 "original", 285 "diff", 286 leftLines, 287 difflib.DiffUtils.diff(leftLines, rightLines), 288 /* contextSize= */ 0); 289 StringBuilder stringBuilder = new StringBuilder(); 290 for (String delta : unifiedDiff) { 291 stringBuilder.append(delta); 292 stringBuilder.append("\n"); 293 } 294 295 return stringBuilder.toString(); 296 } 297 298 /** 299 * Verifies the output of seamendc against the binary policy obtained by secilc-compiling the 300 * CIL policies on the device. The binary policies must be the same. 301 * 302 * @throws Exception 303 */ 304 @Test testSeamendcAgainstSecilc()305 public void testSeamendcAgainstSecilc() throws Exception { 306 File secilcOutWithApex = 307 runSecilc( 308 mPlatPolicyCil, 309 mPlatCompatCil, 310 mSystemExtPolicyCil, 311 mSystemExtMappingCil, 312 mSystemExtCompatCil, 313 mProductPolicyCil, 314 mProductMappingCil, 315 mVendorPolicyCil, 316 mPlatPubVersionedCil, 317 mOdmPolicyCil, 318 mApexSepolicyCil); 319 File secilcOutWithoutApex = 320 runSecilc( 321 mPlatPolicyCil, 322 mPlatCompatCil, 323 mSystemExtPolicyCil, 324 mSystemExtMappingCil, 325 mSystemExtCompatCil, 326 mProductPolicyCil, 327 mProductMappingCil, 328 mVendorPolicyCil, 329 mPlatPubVersionedCil, 330 mOdmPolicyCil); 331 File seamendcOutWithApex = runSeamendc(secilcOutWithoutApex, mApexSepolicyDecompiledCil); 332 333 // system/sepolicy/com.android.sepolicy/33/shell.te 334 assertSourceNotEqual(secilcOutWithoutApex, seamendcOutWithApex, "shell"); 335 assertTargetEqual(secilcOutWithoutApex, seamendcOutWithApex, "shell"); 336 assertSourceEqual(secilcOutWithApex, seamendcOutWithApex, "shell"); 337 assertTargetEqual(secilcOutWithApex, seamendcOutWithApex, "shell"); 338 } 339 } 340