Implement AND ComplexOptCheck
[kconfig-hardened-check.git] / kconfig-hardened-check.py
index 83d1c0d85191d957282e780cb073a18c6727213c..c1a48b8be2bc000815d5e947b7b546156b33ec17 100755 (executable)
@@ -68,16 +68,11 @@ class OptCheck:
         return '{} = {}'.format(self.name, self.state)
 
 
-class OR:
+class ComplexOptCheck:
     def __init__(self, *opts):
         self.opts = opts
         self.result = None
 
-    # self.opts[0] is the option which this OR-check is about.
-    # Use case:
-    #     OR(<X_is_hardened>, <X_is_disabled>)
-    #     OR(<X_is_hardened>, <X_is_hardened_old>)
-
     @property
     def name(self):
         return self.opts[0].name
@@ -98,10 +93,20 @@ class OR:
     def reason(self):
         return self.opts[0].reason
 
+
+class OR(ComplexOptCheck):
+    # self.opts[0] is the option which this OR-check is about.
+    # Use case:
+    #     OR(<X_is_hardened>, <X_is_disabled>)
+    #     OR(<X_is_hardened>, <X_is_hardened_old>)
+
     def check(self):
+        if not self.opts:
+            sys.exit('[!] ERROR: invalid OR check')
+
         for i, opt in enumerate(self.opts):
-            result, msg = opt.check()
-            if result:
+            ret, msg = opt.check()
+            if ret:
                 if i == 0:
                     self.result = opt.result
                 else:
@@ -111,6 +116,24 @@ class OR:
         return False, self.result
 
 
+class AND(ComplexOptCheck):
+    # self.opts[0] is the option which this AND-check is about.
+    # Use case: AND(<suboption>, <main_option>)
+    # Suboption is not checked if checking of the main_option is failed.
+
+    def check(self):
+        for i, opt in reversed(list(enumerate(self.opts))):
+            ret, msg = opt.check()
+            if i == 0:
+                self.result = opt.result
+                return ret, self.result
+            elif not ret:
+                # The requirement is not met. Skip the check.
+                return False, ''
+
+        sys.exit('[!] ERROR: invalid AND check')
+
+
 def detect_arch(fname):
     with open(fname, 'r') as f:
         arch_pattern = re.compile("CONFIG_[a-zA-Z0-9_]*=y")
@@ -231,6 +254,8 @@ def construct_checklist(arch):
         checklist.append(OR(OptCheck('STRICT_DEVMEM',     'y', 'defconfig', 'cut_attack_surface'), \
                             devmem_not_set)) # refers to LOCK_DOWN_KERNEL
 
+    checklist.append(modules_not_set)
+    checklist.append(devmem_not_set)
     checklist.append(OR(OptCheck('IO_STRICT_DEVMEM',  'y', 'kspp', 'cut_attack_surface'), \
                         devmem_not_set)) # refers to LOCK_DOWN_KERNEL
     if debug_mode or arch == 'ARM':
@@ -314,8 +339,9 @@ def print_check_results():
         'option name', 'desired val', 'decision', 'reason', 'check result'))
     print('  ' + '=' * 115)
     for opt in checklist:
-        print('  CONFIG_{:<32}|{:^13}|{:^10}|{:^20}||{:^28}'.format(
-            opt.name, opt.expected, opt.decision, opt.reason, opt.result))
+        if opt.result:
+            print('  CONFIG_{:<32}|{:^13}|{:^10}|{:^20}||{:^28}'.format(
+                opt.name, opt.expected, opt.decision, opt.reason, opt.result))
     print()
 
 
@@ -391,7 +417,7 @@ if __name__ == '__main__':
 
         construct_checklist(arch)
         check_config_file(args.config)
-        error_count = len(list(filter(lambda opt: opt.result.startswith('FAIL'), checklist)))
+        error_count = len(list(filter(lambda opt: opt.result and opt.result.startswith('FAIL'), checklist)))
         if debug_mode:
             sys.exit(0)
         if error_count == 0: