apex_sepolicy_tests: check system/vendor attr

APEX file_contexts are checked against Treble boundary. Eg. vendor apex
can't use system types.

Bug: 387192232
Test: apex_sepolicy_tests -f fc -p vendor
 fails if fc contains system types.
Change-Id: I3773daa8acc4d38a9df869e0d0017f84ce9055da
diff --git a/tests/apex_sepolicy_tests.py b/tests/apex_sepolicy_tests.py
index 26082cb..d8c5c2b 100644
--- a/tests/apex_sepolicy_tests.py
+++ b/tests/apex_sepolicy_tests.py
@@ -29,7 +29,7 @@
 import sys
 import tempfile
 from dataclasses import dataclass
-from typing import List
+from typing import Callable, List
 
 import policy
 
@@ -61,7 +61,12 @@
     pass
 
 
-Matcher = Is | Glob | Regex | BinaryFile
+@dataclass
+class MatchPred:
+    pred: Callable[[str], bool]
+
+
+Matcher = Is | Glob | Regex | BinaryFile | MatchPred
 
 
 # predicate functions for Func matcher
@@ -87,7 +92,13 @@
     labels: set[str]
 
 
-Rule = AllowPerm | ResolveType | NotAnyOf
+@dataclass
+class HasAttr:
+    """Rule checking if the context has the specified attribute"""
+    attr: str
+
+
+Rule = AllowPerm | ResolveType | NotAnyOf | HasAttr
 
 
 # Helper for 'read'
@@ -104,8 +115,10 @@
             return pathlib.PurePath(path).match(pattern)
         case Regex(pattern):
             return re.match(pattern, path)
-        case BinaryFile:
+        case BinaryFile():
             return path.startswith('./bin/') and not path.endswith('/')
+        case MatchPred(pred):
+            return pred(path)
 
 
 def check_rule(pol, path: str, tcontext: str, rule: Rule) -> List[str]:
@@ -129,6 +142,9 @@
         case NotAnyOf(labels):
             if tcontext in labels:
                 errors.append(f"Error: {path}: can't be labelled as '{tcontext}'")
+        case HasAttr(attr):
+            if tcontext not in pol.QueryTypeAttribute(attr, True):
+                errors.append(f"Error: {path}: tcontext({tcontext}) must be associated with {attr}")
     return errors
 
 
@@ -139,7 +155,7 @@
 
 generic_rules = [
     # binaries should be executable
-    (BinaryFile, NotAnyOf({'vendor_file'})),
+    (BinaryFile(), NotAnyOf({'vendor_file'})),
     # permissions
     (Is('./etc/permissions/'), AllowRead('dir', {'system_server'})),
     (Glob('./etc/permissions/*.xml'), AllowRead('file', {'system_server'})),
@@ -159,6 +175,25 @@
 all_rules = target_specific_rules + generic_rules
 
 
+def base_attr_for(partition):
+    if partition in ['system', 'system_ext', 'product']:
+        return 'system_file_type'
+    elif partition in ['vendor', 'odm']:
+        return 'vendor_file_type'
+    else:
+        sys.exit(f"Error: invalid partition: {partition}\n")
+
+
+def system_vendor_rule(partition):
+    exceptions = [
+        "./etc/linkerconfig.pb"
+    ]
+    def pred(path):
+        return path not in exceptions
+
+    return pred, HasAttr(base_attr_for(partition))
+
+
 def check_line(pol: policy.Policy, line: str, rules) -> List[str]:
     """Parses a file_contexts line and runs checks"""
     # skip empty/comment line
@@ -197,7 +232,8 @@
     """Do testing"""
     parser = argparse.ArgumentParser()
     parser.add_argument('--all', action='store_true', help='tests ALL aspects')
-    parser.add_argument('-f', '--file_contexts', help='output of "deapexer list -Z"')
+    parser.add_argument('-f', '--file_contexts', required=True, help='output of "deapexer list -Z"')
+    parser.add_argument('-p', '--partition', help='partition to check Treble violations')
     args = parser.parse_args()
 
     lib_path = extract_data(LIBSEPOLWRAP, work_dir)
@@ -209,6 +245,9 @@
     else:
         rules = generic_rules
 
+    if args.partition:
+        rules.append(system_vendor_rule(args.partition))
+
     errors = []
     with open(args.file_contexts, 'rt', encoding='utf-8') as file_contexts:
         for line in file_contexts:
diff --git a/tests/apex_sepolicy_tests_test.py b/tests/apex_sepolicy_tests_test.py
index 727a023..2a92aee 100644
--- a/tests/apex_sepolicy_tests_test.py
+++ b/tests/apex_sepolicy_tests_test.py
@@ -106,7 +106,7 @@
         self.assert_ok('./bin/init u:object_r:init_exec:s0')
         self.assert_ok('./bin/hw/svc u:object_r:init_exec:s0')
         self.assert_error('./bin/hw/svc u:object_r:vendor_file:s0',
-                          r"Error: .*svc: can\'t be labelled as \'vendor_file\'")
+                          r'Error: .*svc: can\'t be labelled as \'vendor_file\'')
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)