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