blob: ba09819d49283af2283a6d97fbea3c432f97946c [file] [log] [blame]
Andrew Geissler1fe918a2020-05-15 14:16:47 -05001#
2# ISA_kca_plugin.py - Kernel config options analyzer plugin, part of ISA FW
3#
4# Copyright (c) 2015 - 2016, Intel Corporation
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# * Redistributions of source code must retain the above copyright notice,
10# this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in the
13# documentation and/or other materials provided with the distribution.
14# * Neither the name of Intel Corporation nor the names of its contributors
15# may be used to endorse or promote products derived from this software
16# without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29try:
30 from lxml import etree
31except ImportError:
32 try:
33 import xml.etree.cElementTree as etree
34 except ImportError:
35 import xml.etree.ElementTree as etree
36import importlib
37
38KCAnalyzer = None
39
40
41class ISA_KernelChecker():
42 initialized = False
43
44 def __init__(self, ISA_config):
45 self.logfile = ISA_config.logdir + "/isafw_kcalog"
46 self.full_report_name = ISA_config.reportdir + "/kca_full_report_" + \
47 ISA_config.machine + "_" + ISA_config.timestamp
48 self.problems_report_name = ISA_config.reportdir + \
49 "/kca_problems_report_" + ISA_config.machine + "_" + ISA_config.timestamp
50 self.full_reports = ISA_config.full_reports
51 self.initialized = True
52 self.arch = ISA_config.arch
53 with open(self.logfile, 'w') as flog:
54 flog.write("\nPlugin ISA_KernelChecker initialized!\n")
55
56 def append_recommendation(self, report, key, value):
57 report.write("Recommended value:\n")
58 report.write(key + ' : ' + str(value) + '\n')
59 comment = self.comments.get(key, '')
60 if comment != '':
61 report.write("Comment:\n")
62 report.write(comment + '\n')
63
64 def process_kernel(self, ISA_kernel):
65 if (self.initialized):
66 if (ISA_kernel.img_name and ISA_kernel.path_to_config):
67 # Merging common and arch configs
68 common_config_module = importlib.import_module('isafw.isaplugins.configs.kca.{}'.format('common'))
69 arch_config_module = importlib.import_module('isafw.isaplugins.configs.kca.{}'.format(self.arch))
70
71 for c in ["hardening_kco", "keys_kco", "security_kco", "integrity_kco",
72 "hardening_kco_ref", "keys_kco_ref", "security_kco_ref", "integrity_kco_ref",
73 "comments"]:
74 setattr(self, c, merge_config(getattr(arch_config_module, c), getattr(common_config_module, c)))
75 with open(self.logfile, 'a') as flog:
76 flog.write("Analyzing kernel config file at: " + ISA_kernel.path_to_config +
77 " for the image: " + ISA_kernel.img_name + "\n")
78 with open(ISA_kernel.path_to_config, 'r') as fkernel_conf:
79 for line in fkernel_conf:
80 line = line.strip('\n')
81 for key in self.hardening_kco:
82 if key + '=' in line:
83 self.hardening_kco[key] = line.split('=')[1]
84 for key in self.keys_kco:
85 if key + '=' in line:
86 self.keys_kco[key] = line.split('=')[1]
87 for key in self.security_kco:
88 if key + '=' in line:
89 self.security_kco[key] = line.split('=')[1]
90 for key in self.integrity_kco:
91 if key + '=' in line:
92 self.integrity_kco[key] = line.split('=')[1]
93 with open(self.logfile, 'a') as flog:
94 flog.write("\n\nhardening_kco values: " +
95 str(self.hardening_kco))
96 flog.write("\n\nkeys_kco values: " + str(self.keys_kco))
97 flog.write("\n\nsecurity_kco values: " +
98 str(self.security_kco))
99 flog.write("\n\nintegrity_kco values: " +
100 str(self.integrity_kco))
101 self.write_full_report(ISA_kernel)
102 self.write_problems_report(ISA_kernel)
103
104 else:
105 with open(self.logfile, 'a') as flog:
106 flog.write(
107 "Mandatory arguments such as image name and path to config are not provided!\n")
108 flog.write("Not performing the call.\n")
109 else:
110 with open(self.logfile, 'a') as flog:
111 flog.write(
112 "Plugin hasn't initialized! Not performing the call!\n")
113
114 def write_full_report(self, ISA_kernel):
115 if self.full_reports:
116 with open(self.full_report_name + "_" + ISA_kernel.img_name, 'w') as freport:
117 freport.write("Report for image: " +
118 ISA_kernel.img_name + '\n')
119 freport.write("With the kernel conf at: " +
120 ISA_kernel.path_to_config + '\n\n')
121 freport.write("Hardening options:\n")
122 for key in sorted(self.hardening_kco):
123 freport.write(
124 key + ' : ' + str(self.hardening_kco[key]) + '\n')
125 freport.write("\nKey-related options:\n")
126 for key in sorted(self.keys_kco):
127 freport.write(key + ' : ' + str(self.keys_kco[key]) + '\n')
128 freport.write("\nSecurity options:\n")
129 for key in sorted(self.security_kco):
130 freport.write(
131 key + ' : ' + str(self.security_kco[key]) + '\n')
132 freport.write("\nIntegrity options:\n")
133 for key in sorted(self.integrity_kco):
134 freport.write(
135 key + ' : ' + str(self.integrity_kco[key]) + '\n')
136
137 def write_problems_report(self, ISA_kernel):
138 self.write_text_problems_report(ISA_kernel)
139 self.write_xml_problems_report(ISA_kernel)
140
141 def write_text_problems_report(self, ISA_kernel):
142 with open(self.problems_report_name + "_" + ISA_kernel.img_name, 'w') as freport:
143 freport.write("Report for image: " + ISA_kernel.img_name + '\n')
144 freport.write("With the kernel conf at: " +
145 ISA_kernel.path_to_config + '\n\n')
146 freport.write("Hardening options that need improvement:\n")
147 for key in sorted(self.hardening_kco):
148 if (self.hardening_kco[key] != self.hardening_kco_ref[key]):
149 valid = False
150 if (key == "CONFIG_CMDLINE"):
151 if (len(self.hardening_kco['CONFIG_CMDLINE']) > 0):
152 valid = True
153 if (key == "CONFIG_DEBUG_STRICT_USER_COPY_CHECKS"):
154 if (self.hardening_kco['CONFIG_ARCH_HAS_DEBUG_STRICT_USER_COPY_CHECKS'] == 'y'):
155 valid = True
156 if (key == "CONFIG_RANDOMIZE_BASE_MAX_OFFSET"):
157 options = self.hardening_kco_ref[key].split(',')
158 for option in options:
159 if (option == self.hardening_kco[key]):
160 valid = True
161 break
162 if not valid:
163 freport.write("\nActual value:\n")
164 freport.write(
165 key + ' : ' + str(self.hardening_kco[key]) + '\n')
166 self.append_recommendation(freport, key, self.hardening_kco_ref[key])
167 freport.write("\nKey-related options that need improvement:\n")
168 for key in sorted(self.keys_kco):
169 if (self.keys_kco[key] != self.keys_kco_ref[key]):
170 freport.write("\nActual value:\n")
171 freport.write(key + ' : ' + str(self.keys_kco[key]) + '\n')
172 self.append_recommendation(freport, key, self.keys_kco_ref[key])
173 freport.write("\nSecurity options that need improvement:\n")
174 for key in sorted(self.security_kco):
175 if (self.security_kco[key] != self.security_kco_ref[key]):
176 valid = False
177 if (key == "CONFIG_DEFAULT_SECURITY"):
178 options = self.security_kco_ref[key].split(',')
179 for option in options:
180 if (option == self.security_kco[key]):
181 valid = True
182 break
183 if ((key == "CONFIG_SECURITY_SELINUX") or
184 (key == "CONFIG_SECURITY_SMACK") or
185 (key == "CONFIG_SECURITY_APPARMOR") or
186 (key == "CONFIG_SECURITY_TOMOYO")):
187 if ((self.security_kco['CONFIG_SECURITY_SELINUX'] == 'y') or
188 (self.security_kco['CONFIG_SECURITY_SMACK'] == 'y') or
189 (self.security_kco['CONFIG_SECURITY_APPARMOR'] == 'y') or
190 (self.security_kco['CONFIG_SECURITY_TOMOYO'] == 'y')):
191 valid = True
192 if not valid:
193 freport.write("\nActual value:\n")
194 freport.write(
195 key + ' : ' + str(self.security_kco[key]) + '\n')
196 self.append_recommendation(freport, key, self.security_kco_ref[key])
197 freport.write("\nIntegrity options that need improvement:\n")
198 for key in sorted(self.integrity_kco):
199 if (self.integrity_kco[key] != self.integrity_kco_ref[key]):
200 valid = False
201 if ((key == "CONFIG_IMA_DEFAULT_HASH_SHA1") or
202 (key == "CONFIG_IMA_DEFAULT_HASH_SHA256") or
203 (key == "CONFIG_IMA_DEFAULT_HASH_SHA512") or
204 (key == "CONFIG_IMA_DEFAULT_HASH_WP512")):
205 if ((self.integrity_kco['CONFIG_IMA_DEFAULT_HASH_SHA256'] == 'y') or
206 (self.integrity_kco['CONFIG_IMA_DEFAULT_HASH_SHA512'] == 'y')):
207 valid = True
208 if not valid:
209 freport.write("\nActual value:\n")
210 freport.write(
211 key + ' : ' + str(self.integrity_kco[key]) + '\n')
212 self.append_recommendation(freport, key, self.integrity_kco_ref[key])
213
214 def write_xml_problems_report(self, ISA_kernel):
215 # write_problems_report_xml
216 num_tests = len(self.hardening_kco) + len(self.keys_kco) + \
217 len(self.security_kco) + len(self.integrity_kco)
218 root = etree.Element(
219 'testsuite', name='KCA_Plugin', tests=str(num_tests))
220 for key in sorted(self.hardening_kco):
221 tcase1 = etree.SubElement(
222 root, 'testcase', classname='Hardening options', name=key)
223 if (self.hardening_kco[key] != self.hardening_kco_ref[key]):
224 valid = False
225 if (key == "CONFIG_CMDLINE"):
226 if (len(self.hardening_kco['CONFIG_CMDLINE']) > 0):
227 valid = True
228 if (key == "CONFIG_DEBUG_STRICT_USER_COPY_CHECKS"):
229 if (self.hardening_kco['CONFIG_ARCH_HAS_DEBUG_STRICT_USER_COPY_CHECKS'] == 'y'):
230 valid = True
231 if (key == "CONFIG_RANDOMIZE_BASE_MAX_OFFSET"):
232 options = self.hardening_kco_ref[key].split(',')
233 for option in options:
234 if (option == self.hardening_kco[key]):
235 valid = True
236 break
237 if not valid:
238 msg1 = 'current=' + key + ' is ' + \
239 str(self.hardening_kco[
240 key]) + ', recommended=' + key + ' is ' + str(self.hardening_kco_ref[key])
241 etree.SubElement(
242 tcase1, 'failure', message=msg1, type='violation')
243 for key in sorted(self.keys_kco):
244 tcase2 = etree.SubElement(
245 root, 'testcase', classname='Key-related options', name=key)
246 if (self.keys_kco[key] != self.keys_kco_ref[key]):
247 msg2 = 'current=' + key + ' is ' + \
248 str(self.keys_kco[key] + ', recommended=' +
249 key + ' is ' + str(self.keys_kco_ref[key]))
250 etree.SubElement(
251 tcase2, 'failure', message=msg2, type='violation')
252 for key in sorted(self.security_kco):
253 tcase3 = etree.SubElement(
254 root, 'testcase', classname='Security options', name=key)
255 if (self.security_kco[key] != self.security_kco_ref[key]):
256 valid = False
257 if (key == "CONFIG_DEFAULT_SECURITY"):
258 options = self.security_kco_ref[key].split(',')
259 for option in options:
260 if (option == self.security_kco[key]):
261 valid = True
262 break
263 if ((key == "CONFIG_SECURITY_SELINUX") or
264 (key == "CONFIG_SECURITY_SMACK") or
265 (key == "CONFIG_SECURITY_APPARMOR") or
266 (key == "CONFIG_SECURITY_TOMOYO")):
267 if ((self.security_kco['CONFIG_SECURITY_SELINUX'] == 'y') or
268 (self.security_kco['CONFIG_SECURITY_SMACK'] == 'y') or
269 (self.security_kco['CONFIG_SECURITY_APPARMOR'] == 'y') or
270 (self.security_kco['CONFIG_SECURITY_TOMOYO'] == 'y')):
271 valid = True
272 if not valid:
273 msg3 = 'current=' + key + ' is ' + \
274 str(self.security_kco[key]) + ', recommended=' + \
275 key + ' is ' + str(self.security_kco_ref[key])
276 etree.SubElement(
277 tcase3, 'failure', message=msg3, type='violation')
278 for key in sorted(self.integrity_kco):
279 tcase4 = etree.SubElement(
280 root, 'testcase', classname='Integrity options', name=key)
281 if (self.integrity_kco[key] != self.integrity_kco_ref[key]):
282 valid = False
283 if ((key == "CONFIG_IMA_DEFAULT_HASH_SHA1") or
284 (key == "CONFIG_IMA_DEFAULT_HASH_SHA256") or
285 (key == "CONFIG_IMA_DEFAULT_HASH_SHA512") or
286 (key == "CONFIG_IMA_DEFAULT_HASH_WP512")):
287 if ((self.integrity_kco['CONFIG_IMA_DEFAULT_HASH_SHA256'] == 'y') or
288 (self.integrity_kco['CONFIG_IMA_DEFAULT_HASH_SHA512'] == 'y')):
289 valid = True
290 if not valid:
291 msg4 = 'current=' + key + ' is ' + \
292 str(self.integrity_kco[
293 key]) + ', recommended=' + key + ' is ' + str(self.integrity_kco_ref[key])
294 etree.SubElement(
295 tcase4, 'failure', message=msg4, type='violation')
296 tree = etree.ElementTree(root)
297 output = self.problems_report_name + "_" + ISA_kernel.img_name + '.xml'
298 try:
299 tree.write(output, encoding='UTF-8',
300 pretty_print=True, xml_declaration=True)
301 except TypeError:
302 tree.write(output, encoding='UTF-8', xml_declaration=True)
303
304
305def merge_config(arch_kco, common_kco):
306 merged = arch_kco.copy()
307 merged.update(common_kco)
308 return merged
309
310# ======== supported callbacks from ISA ============= #
311def init(ISA_config):
312 global KCAnalyzer
313 KCAnalyzer = ISA_KernelChecker(ISA_config)
314
315
316def getPluginName():
317 return "ISA_KernelChecker"
318
319
320def process_kernel(ISA_kernel):
321 global KCAnalyzer
322 return KCAnalyzer.process_kernel(ISA_kernel)
323# ==================================================== #