1 /* 2 * Copyright (C) 2024 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 17 package android.security.cts; 18 19 import static org.junit.Assert.assertTrue; 20 21 import com.android.compatibility.common.util.PropertyUtil; 22 import com.android.tradefed.device.ITestDevice; 23 24 import java.io.BufferedReader; 25 import java.io.File; 26 import java.io.InputStreamReader; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 import java.util.stream.Collectors; 35 import java.util.stream.Stream; 36 37 class SELinuxNeverallowRule { 38 private static String[] sConditions = { 39 "TREBLE_ONLY", 40 "COMPATIBLE_PROPERTY_ONLY", 41 "LAUNCHING_WITH_R_ONLY", 42 "LAUNCHING_WITH_S_ONLY", 43 }; 44 45 public String mText; 46 public boolean fullTrebleOnly; 47 public boolean launchingWithROnly; 48 public boolean launchingWithSOnly; 49 public boolean compatiblePropertyOnly; 50 SELinuxNeverallowRule(String text, Map<String, Integer> conditions)51 private SELinuxNeverallowRule(String text, Map<String, Integer> conditions) { 52 mText = text; 53 if (conditions.getOrDefault("TREBLE_ONLY", 0) > 0) { 54 fullTrebleOnly = true; 55 } 56 if (conditions.getOrDefault("COMPATIBLE_PROPERTY_ONLY", 0) > 0) { 57 compatiblePropertyOnly = true; 58 } 59 if (conditions.getOrDefault("LAUNCHING_WITH_R_ONLY", 0) > 0) { 60 launchingWithROnly = true; 61 } 62 if (conditions.getOrDefault("LAUNCHING_WITH_S_ONLY", 0) > 0) { 63 launchingWithSOnly = true; 64 } 65 } 66 toString()67 public String toString() { 68 return "Rule [text= " + mText 69 + ", fullTrebleOnly=" + fullTrebleOnly 70 + ", compatiblePropertyOnly=" + compatiblePropertyOnly 71 + ", launchingWithROnly=" + launchingWithROnly 72 + ", launchingWithSOnly=" + launchingWithSOnly 73 + "]"; 74 } 75 isFullTrebleDevice(ITestDevice device)76 private boolean isFullTrebleDevice(ITestDevice device) throws Exception { 77 return SELinuxHostTest.isFullTrebleDevice(device); 78 } 79 isDeviceLaunchingWithR(ITestDevice device)80 private boolean isDeviceLaunchingWithR(ITestDevice device) throws Exception { 81 return PropertyUtil.getFirstApiLevel(device) > 29; 82 } 83 isDeviceLaunchingWithS(ITestDevice device)84 private boolean isDeviceLaunchingWithS(ITestDevice device) throws Exception { 85 return PropertyUtil.getFirstApiLevel(device) > 30; 86 } 87 isCompatiblePropertyEnforcedDevice(ITestDevice device)88 private boolean isCompatiblePropertyEnforcedDevice(ITestDevice device) throws Exception { 89 return SELinuxHostTest.isCompatiblePropertyEnforcedDevice(device); 90 } 91 isCompatible(ITestDevice device)92 public boolean isCompatible(ITestDevice device) throws Exception { 93 if ((fullTrebleOnly) && (!isFullTrebleDevice(device))) { 94 // This test applies only to Treble devices but this device isn't one 95 return false; 96 } 97 if ((launchingWithROnly) && (!isDeviceLaunchingWithR(device))) { 98 // This test applies only to devices launching with R or later but this device isn't one 99 return false; 100 } 101 if ((launchingWithSOnly) && (!isDeviceLaunchingWithS(device))) { 102 // This test applies only to devices launching with S or later but this device isn't one 103 return false; 104 } 105 if ((compatiblePropertyOnly) && (!isCompatiblePropertyEnforcedDevice(device))) { 106 // This test applies only to devices on which compatible property is enforced but this 107 // device isn't one 108 return false; 109 } 110 return true; 111 } 112 parsePolicy(String policy)113 public static List<SELinuxNeverallowRule> parsePolicy(String policy) throws Exception { 114 String patternConditions = Arrays.stream(sConditions) 115 .flatMap(condition -> Stream.of("BEGIN_" + condition, "END_" + condition)) 116 .collect(Collectors.joining("|")); 117 118 /* Uncomment conditions delimiter lines. */ 119 Pattern uncommentConditions = Pattern.compile("^\\s*#\\s*(" + patternConditions + ")\\s*$", 120 Pattern.MULTILINE); 121 Matcher matcher = uncommentConditions.matcher(policy); 122 policy = matcher.replaceAll("$1"); 123 124 /* Remove all comments. */ 125 Pattern comments = Pattern.compile("#.*?$", Pattern.MULTILINE); 126 matcher = comments.matcher(policy); 127 policy = matcher.replaceAll(""); 128 129 /* Use a pattern to match all the neverallow rules or a condition. */ 130 Pattern neverAllowPattern = Pattern.compile( 131 "(neverallow\\s[^;]+?;|" + patternConditions + ")", 132 Pattern.MULTILINE); 133 134 List<SELinuxNeverallowRule> rules = new ArrayList(); 135 Map<String, Integer> conditions = new HashMap(); 136 137 matcher = neverAllowPattern.matcher(policy); 138 while (matcher.find()) { 139 String rule = matcher.group(1).replace("\n", " "); 140 if (rule.startsWith("BEGIN_")) { 141 String section = rule.substring(6); 142 conditions.put(section, conditions.getOrDefault(section, 0) + 1); 143 } else if (rule.startsWith("END_")) { 144 String section = rule.substring(4); 145 Integer v = conditions.getOrDefault(section, 0); 146 assertTrue("Condition " + rule + " found without BEGIN", v > 0); 147 conditions.put(section, v - 1); 148 } else if (rule.startsWith("neverallow")) { 149 rules.add(new SELinuxNeverallowRule(rule, conditions)); 150 } else { 151 throw new Exception("Unknown rule: " + rule); 152 } 153 } 154 155 for (Map.Entry<String, Integer> condition : conditions.entrySet()) { 156 if (condition.getValue() != 0) { 157 throw new Exception("End of input while inside " + condition.getKey() + " section"); 158 } 159 } 160 161 return rules; 162 } 163 testNeverallowRule(File sepolicyAnalyze, File policyFile)164 public void testNeverallowRule(File sepolicyAnalyze, File policyFile) throws Exception { 165 /* run sepolicy-analyze neverallow check on policy file using given neverallow rules */ 166 ProcessBuilder pb = new ProcessBuilder(sepolicyAnalyze.getAbsolutePath(), 167 policyFile.getAbsolutePath(), "neverallow", "-n", 168 mText); 169 pb.redirectOutput(ProcessBuilder.Redirect.PIPE); 170 pb.redirectErrorStream(true); 171 Process p = pb.start(); 172 BufferedReader result = new BufferedReader(new InputStreamReader(p.getInputStream())); 173 String line; 174 StringBuilder errorString = new StringBuilder(); 175 while ((line = result.readLine()) != null) { 176 errorString.append(line); 177 errorString.append("\n"); 178 } 179 p.waitFor(); 180 assertTrue("The following errors were encountered when validating the SELinux" 181 + "neverallow rule:\n" + mText + "\n" + errorString, 182 errorString.length() == 0); 183 } 184 } 185