diff options
| author | Armin Kuster <akuster808@gmail.com> | 2020-07-05 19:13:06 +0000 |
|---|---|---|
| committer | Armin Kuster <akuster808@gmail.com> | 2020-07-06 09:41:26 -0700 |
| commit | 547f552c853056c117a08fb88636b9cf0919121d (patch) | |
| tree | c1fc83b928eb502c170e091f8b7e2543721b27a8 /meta-security-isafw | |
| parent | a2a102b2de68f31df5a3b46665c8afb2d28c0219 (diff) | |
| download | meta-security-547f552c853056c117a08fb88636b9cf0919121d.tar.gz | |
isafw.bbclass: typo in layer name
move class to proper layer
Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'meta-security-isafw')
| -rw-r--r-- | meta-security-isafw/classes/isafw.bbclass | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/meta-security-isafw/classes/isafw.bbclass b/meta-security-isafw/classes/isafw.bbclass new file mode 100644 index 0000000..146acdf --- /dev/null +++ b/meta-security-isafw/classes/isafw.bbclass | |||
| @@ -0,0 +1,318 @@ | |||
| 1 | # Security scanning class | ||
| 2 | # | ||
| 3 | # Based in part on buildhistory.bbclass which was in turn based on | ||
| 4 | # testlab.bbclass and packagehistory.bbclass | ||
| 5 | # | ||
| 6 | # Copyright (C) 2011-2015 Intel Corporation | ||
| 7 | # Copyright (C) 2007-2011 Koen Kooi <koen@openembedded.org> | ||
| 8 | # | ||
| 9 | |||
| 10 | LICENSE = "MIT" | ||
| 11 | |||
| 12 | require conf/distro/include/distro_alias.inc | ||
| 13 | |||
| 14 | ISAFW_WORKDIR = "${WORKDIR}/isafw" | ||
| 15 | ISAFW_REPORTDIR ?= "${LOG_DIR}/isafw-report" | ||
| 16 | ISAFW_LOGDIR ?= "${LOG_DIR}/isafw-logs" | ||
| 17 | |||
| 18 | ISAFW_PLUGINS_WHITELIST ?= "" | ||
| 19 | ISAFW_PLUGINS_BLACKLIST ?= "" | ||
| 20 | |||
| 21 | ISAFW_LA_PLUGIN_IMAGE_WHITELIST ?= "" | ||
| 22 | ISAFW_LA_PLUGIN_IMAGE_BLACKLIST ?= "" | ||
| 23 | |||
| 24 | # First, code to handle scanning each recipe that goes into the build | ||
| 25 | |||
| 26 | do_analysesource[nostamp] = "1" | ||
| 27 | do_analysesource[cleandirs] = "${ISAFW_WORKDIR}" | ||
| 28 | |||
| 29 | python do_analysesource() { | ||
| 30 | from isafw import isafw | ||
| 31 | |||
| 32 | imageSecurityAnalyser = isafw_init(isafw, d) | ||
| 33 | |||
| 34 | if not d.getVar('SRC_URI', True): | ||
| 35 | # Recipe didn't fetch any sources, nothing to do here I assume? | ||
| 36 | return | ||
| 37 | |||
| 38 | recipe = isafw.ISA_package() | ||
| 39 | recipe.name = d.getVar('BPN', True) | ||
| 40 | recipe.version = d.getVar('PV', True) | ||
| 41 | recipe.version = recipe.version.split('+git', 1)[0] | ||
| 42 | |||
| 43 | for p in d.getVar('PACKAGES', True).split(): | ||
| 44 | license = str(d.getVar('LICENSE_' + p, True)) | ||
| 45 | if license == "None": | ||
| 46 | license = d.getVar('LICENSE', True) | ||
| 47 | license = license.replace("(", "") | ||
| 48 | license = license.replace(")", "") | ||
| 49 | licenses = license.split() | ||
| 50 | while '|' in licenses: | ||
| 51 | licenses.remove('|') | ||
| 52 | while '&' in licenses: | ||
| 53 | licenses.remove('&') | ||
| 54 | for l in licenses: | ||
| 55 | recipe.licenses.append(p + ":" + canonical_license(d, l)) | ||
| 56 | |||
| 57 | aliases = d.getVar('DISTRO_PN_ALIAS', True) | ||
| 58 | if aliases: | ||
| 59 | recipe.aliases = aliases.split() | ||
| 60 | faliases = [] | ||
| 61 | for a in recipe.aliases: | ||
| 62 | if (a != "OSPDT") and (not (a.startswith("upstream="))): | ||
| 63 | faliases.append(a.split('=', 1)[-1]) | ||
| 64 | # remove possible duplicates in pkg names | ||
| 65 | faliases = list(set(faliases)) | ||
| 66 | recipe.aliases = faliases | ||
| 67 | |||
| 68 | for patch in src_patches(d): | ||
| 69 | _,_,local,_,_,_=bb.fetch.decodeurl(patch) | ||
| 70 | recipe.patch_files.append(os.path.basename(local)) | ||
| 71 | if (not recipe.patch_files) : | ||
| 72 | recipe.patch_files.append("None") | ||
| 73 | |||
| 74 | # Pass the recipe object to the security framework | ||
| 75 | bb.debug(1, '%s: analyse sources' % (d.getVar('PN', True))) | ||
| 76 | imageSecurityAnalyser.process_package(recipe) | ||
| 77 | |||
| 78 | return | ||
| 79 | } | ||
| 80 | |||
| 81 | addtask do_analysesource before do_build | ||
| 82 | |||
| 83 | # This task intended to be called after default task to process reports | ||
| 84 | |||
| 85 | PR_ORIG_TASK := "${BB_DEFAULT_TASK}" | ||
| 86 | addhandler process_reports_handler | ||
| 87 | process_reports_handler[eventmask] = "bb.event.BuildCompleted" | ||
| 88 | |||
| 89 | python process_reports_handler() { | ||
| 90 | from isafw import isafw | ||
| 91 | |||
| 92 | dd = d.createCopy() | ||
| 93 | target_sysroot = dd.expand("${STAGING_DIR}/${MACHINE}") | ||
| 94 | native_sysroot = dd.expand("${STAGING_DIR}/${BUILD_ARCH}") | ||
| 95 | staging_populate_sysroot_dir(target_sysroot, native_sysroot, True, dd) | ||
| 96 | |||
| 97 | dd.setVar("STAGING_DIR_NATIVE", native_sysroot) | ||
| 98 | savedenv = os.environ.copy() | ||
| 99 | os.environ["PATH"] = dd.getVar("PATH", True) | ||
| 100 | |||
| 101 | imageSecurityAnalyser = isafw_init(isafw, dd) | ||
| 102 | bb.debug(1, 'isafw: process reports') | ||
| 103 | imageSecurityAnalyser.process_report() | ||
| 104 | |||
| 105 | os.environ["PATH"] = savedenv["PATH"] | ||
| 106 | } | ||
| 107 | |||
| 108 | do_build[depends] += "cve-update-db-native:do_populate_cve_db ca-certificates-native:do_populate_sysroot" | ||
| 109 | do_build[depends] += "python3-lxml-native:do_populate_sysroot" | ||
| 110 | |||
| 111 | # These tasks are intended to be called directly by the user (e.g. bitbake -c) | ||
| 112 | |||
| 113 | addtask do_analyse_sources after do_analysesource | ||
| 114 | do_analyse_sources[doc] = "Produce ISAFW reports based on given package without building it" | ||
| 115 | do_analyse_sources[nostamp] = "1" | ||
| 116 | do_analyse_sources() { | ||
| 117 | : | ||
| 118 | } | ||
| 119 | |||
| 120 | addtask do_analyse_sources_all after do_analysesource | ||
| 121 | do_analyse_sources_all[doc] = "Produce ISAFW reports for all packages in given target without building them" | ||
| 122 | do_analyse_sources_all[recrdeptask] = "do_analyse_sources_all do_analysesource" | ||
| 123 | do_analyse_sources_all[recideptask] = "do_${PR_ORIG_TASK}" | ||
| 124 | do_analyse_sources_all[nostamp] = "1" | ||
| 125 | do_analyse_sources_all() { | ||
| 126 | : | ||
| 127 | } | ||
| 128 | |||
| 129 | python() { | ||
| 130 | # We probably don't need to scan these | ||
| 131 | if bb.data.inherits_class('native', d) or \ | ||
| 132 | bb.data.inherits_class('nativesdk', d) or \ | ||
| 133 | bb.data.inherits_class('cross', d) or \ | ||
| 134 | bb.data.inherits_class('crosssdk', d) or \ | ||
| 135 | bb.data.inherits_class('cross-canadian', d) or \ | ||
| 136 | bb.data.inherits_class('packagegroup', d) or \ | ||
| 137 | bb.data.inherits_class('image', d): | ||
| 138 | bb.build.deltask('do_analysesource', d) | ||
| 139 | } | ||
| 140 | |||
| 141 | fakeroot python do_analyse_image() { | ||
| 142 | |||
| 143 | from isafw import isafw | ||
| 144 | |||
| 145 | imageSecurityAnalyser = isafw_init(isafw, d) | ||
| 146 | |||
| 147 | # Directory where the image's entire contents can be examined | ||
| 148 | rootfsdir = d.getVar('IMAGE_ROOTFS', True) | ||
| 149 | |||
| 150 | imagebasename = d.getVar('IMAGE_BASENAME', True) | ||
| 151 | |||
| 152 | kernelconf = d.getVar('STAGING_KERNEL_BUILDDIR', True) + "/.config" | ||
| 153 | if os.path.exists(kernelconf): | ||
| 154 | kernel = isafw.ISA_kernel() | ||
| 155 | kernel.img_name = imagebasename | ||
| 156 | kernel.path_to_config = kernelconf | ||
| 157 | bb.debug(1, 'do kernel conf analysis on %s' % kernelconf) | ||
| 158 | imageSecurityAnalyser.process_kernel(kernel) | ||
| 159 | else: | ||
| 160 | bb.debug(1, 'Kernel configuration file is missing. Not performing analysis on %s' % kernelconf) | ||
| 161 | |||
| 162 | pkglist = manifest2pkglist(d) | ||
| 163 | |||
| 164 | imagebasename = d.getVar('IMAGE_BASENAME', True) | ||
| 165 | |||
| 166 | if (pkglist): | ||
| 167 | pkg_list = isafw.ISA_pkg_list() | ||
| 168 | pkg_list.img_name = imagebasename | ||
| 169 | pkg_list.path_to_list = pkglist | ||
| 170 | bb.debug(1, 'do pkg list analysis on %s' % pkglist) | ||
| 171 | imageSecurityAnalyser.process_pkg_list(pkg_list) | ||
| 172 | |||
| 173 | fs = isafw.ISA_filesystem() | ||
| 174 | fs.img_name = imagebasename | ||
| 175 | fs.path_to_fs = rootfsdir | ||
| 176 | |||
| 177 | bb.debug(1, 'do image analysis on %s' % rootfsdir) | ||
| 178 | imageSecurityAnalyser.process_filesystem(fs) | ||
| 179 | } | ||
| 180 | |||
| 181 | do_rootfs[depends] += "checksec-native:do_populate_sysroot ca-certificates-native:do_populate_sysroot" | ||
| 182 | do_rootfs[depends] += "prelink-native:do_populate_sysroot" | ||
| 183 | do_rootfs[depends] += "python3-lxml-native:do_populate_sysroot" | ||
| 184 | |||
| 185 | isafw_init[vardepsexclude] = "DATETIME" | ||
| 186 | def isafw_init(isafw, d): | ||
| 187 | import re, errno | ||
| 188 | |||
| 189 | isafw_config = isafw.ISA_config() | ||
| 190 | # Override the builtin default in curl-native (used by cve-update-db-nativ) | ||
| 191 | # because that default is a path that may not be valid: when curl-native gets | ||
| 192 | # installed from sstate, we end up with the sysroot path as it was on the | ||
| 193 | # original build host, which is not necessarily the same path used now | ||
| 194 | # (see https://bugzilla.yoctoproject.org/show_bug.cgi?id=9883). | ||
| 195 | # | ||
| 196 | # Can't use ${sysconfdir} here, it already includes ${STAGING_DIR_NATIVE} | ||
| 197 | # when the current recipe is native. | ||
| 198 | isafw_config.cacert = d.expand('${STAGING_DIR_NATIVE}/etc/ssl/certs/ca-certificates.crt') | ||
| 199 | |||
| 200 | bb.utils.export_proxies(d) | ||
| 201 | |||
| 202 | isafw_config.machine = d.getVar('MACHINE', True) | ||
| 203 | isafw_config.timestamp = d.getVar('DATETIME', True) | ||
| 204 | isafw_config.reportdir = d.getVar('ISAFW_REPORTDIR', True) + "_" + isafw_config.timestamp | ||
| 205 | if not os.path.exists(os.path.dirname(isafw_config.reportdir + "/test")): | ||
| 206 | try: | ||
| 207 | os.makedirs(os.path.dirname(isafw_config.reportdir + "/test")) | ||
| 208 | except OSError as exc: | ||
| 209 | if exc.errno == errno.EEXIST and os.path.isdir(isafw_config.reportdir): | ||
| 210 | pass | ||
| 211 | else: raise | ||
| 212 | isafw_config.logdir = d.getVar('ISAFW_LOGDIR', True) | ||
| 213 | # Adding support for arm | ||
| 214 | # TODO: Add support for other platforms | ||
| 215 | isafw_config.arch = d.getVar('TARGET_ARCH', True) | ||
| 216 | if ( isafw_config.arch != "arm" ): | ||
| 217 | isafw_config.arch = "x86" | ||
| 218 | |||
| 219 | whitelist = d.getVar('ISAFW_PLUGINS_WHITELIST', True) | ||
| 220 | blacklist = d.getVar('ISAFW_PLUGINS_BLACKLIST', True) | ||
| 221 | if whitelist: | ||
| 222 | isafw_config.plugin_whitelist = re.split(r'[,\s]*', whitelist) | ||
| 223 | if blacklist: | ||
| 224 | isafw_config.plugin_blacklist = re.split(r'[,\s]*', blacklist) | ||
| 225 | |||
| 226 | la_image_whitelist = d.getVar('ISAFW_LA_PLUGIN_IMAGE_WHITELIST', True) | ||
| 227 | la_image_blacklist = d.getVar('ISAFW_LA_PLUGIN_IMAGE_BLACKLIST', True) | ||
| 228 | if la_image_whitelist: | ||
| 229 | isafw_config.la_plugin_image_whitelist = re.split(r'[,\s]*', la_image_whitelist) | ||
| 230 | if la_image_blacklist: | ||
| 231 | isafw_config.la_plugin_image_blacklist = re.split(r'[,\s]*', la_image_blacklist) | ||
| 232 | |||
| 233 | return isafw.ISA(isafw_config) | ||
| 234 | |||
| 235 | # based on toaster.bbclass _toaster_load_pkgdatafile function | ||
| 236 | def binary2source(dirpath, filepath): | ||
| 237 | import re | ||
| 238 | originPkg = "" | ||
| 239 | with open(os.path.join(dirpath, filepath), "r") as fin: | ||
| 240 | for line in fin: | ||
| 241 | try: | ||
| 242 | kn, kv = line.strip().split(": ", 1) | ||
| 243 | m = re.match(r"^PKG_([^A-Z:]*)", kn) | ||
| 244 | if m: | ||
| 245 | originPkg = str(m.group(1)) | ||
| 246 | except ValueError: | ||
| 247 | pass # ignore lines without valid key: value pairs: | ||
| 248 | if not originPkg: | ||
| 249 | originPkg = "UNKNOWN" | ||
| 250 | return originPkg | ||
| 251 | |||
| 252 | manifest2pkglist[vardepsexclude] = "DATETIME" | ||
| 253 | def manifest2pkglist(d): | ||
| 254 | import glob | ||
| 255 | |||
| 256 | manifest_file = d.getVar('IMAGE_MANIFEST', True) | ||
| 257 | imagebasename = d.getVar('IMAGE_BASENAME', True) | ||
| 258 | reportdir = d.getVar('ISAFW_REPORTDIR', True) + "_" + d.getVar('DATETIME', True) | ||
| 259 | pkgdata_dir = d.getVar("PKGDATA_DIR", True) | ||
| 260 | rr_dir = "%s/runtime-reverse/" % pkgdata_dir | ||
| 261 | pkglist = reportdir + "/pkglist" | ||
| 262 | |||
| 263 | with open(pkglist, 'a') as foutput: | ||
| 264 | foutput.write("Packages for image " + imagebasename + "\n") | ||
| 265 | try: | ||
| 266 | with open(manifest_file, 'r') as finput: | ||
| 267 | for line in finput: | ||
| 268 | items = line.split() | ||
| 269 | if items and (len(items) >= 3): | ||
| 270 | pkgnames = map(os.path.basename, glob.glob(os.path.join(rr_dir, items[0]))) | ||
| 271 | for pkgname in pkgnames: | ||
| 272 | originPkg = binary2source(rr_dir, pkgname) | ||
| 273 | version = items[2] | ||
| 274 | if not version: | ||
| 275 | version = "undetermined" | ||
| 276 | foutput.write(pkgname + " " + version + " " + originPkg + "\n") | ||
| 277 | except IOError: | ||
| 278 | bb.debug(1, 'isafw: manifest file not found. Skip pkg list analysis') | ||
| 279 | return ""; | ||
| 280 | |||
| 281 | |||
| 282 | return pkglist | ||
| 283 | |||
| 284 | # NOTE: by the time IMAGE_POSTPROCESS_COMMAND items are called, the image | ||
| 285 | # has been stripped of the package manager database (if runtime package management | ||
| 286 | # is not enabled, i.e. 'package-management' is not in IMAGE_FEATURES). If you | ||
| 287 | # do want to be using the package manager to operate on the image contents, you'll | ||
| 288 | # need to call your function from ROOTFS_POSTINSTALL_COMMAND or | ||
| 289 | # ROOTFS_POSTUNINSTALL_COMMAND instead - however if you do that you should then be | ||
| 290 | # aware that what you'll be looking at isn't exactly what you will see in the image | ||
| 291 | # at runtime (there will be other postprocessing functions called after yours). | ||
| 292 | # | ||
| 293 | # do_analyse_image does not need the package manager database. Making it | ||
| 294 | # a separate task instead of a IMAGE_POSTPROCESS_COMMAND has several | ||
| 295 | # advantages: | ||
| 296 | # - all other image commands are guaranteed to have completed | ||
| 297 | # - it can run in parallel to other tasks which depend on the complete | ||
| 298 | # image, instead of blocking those other tasks | ||
| 299 | # - meta-swupd helper images do not need to be analysed and won't be | ||
| 300 | # because nothing depends on their "do_build" task, only on | ||
| 301 | # do_image_complete | ||
| 302 | python () { | ||
| 303 | if bb.data.inherits_class('image', d): | ||
| 304 | bb.build.addtask('do_analyse_image', 'do_build', 'do_image_complete', d) | ||
| 305 | } | ||
| 306 | |||
| 307 | python isafwreport_handler () { | ||
| 308 | |||
| 309 | import shutil | ||
| 310 | |||
| 311 | logdir = e.data.getVar('ISAFW_LOGDIR', True) | ||
| 312 | if os.path.exists(os.path.dirname(logdir+"/test")): | ||
| 313 | shutil.rmtree(logdir) | ||
| 314 | os.makedirs(os.path.dirname(logdir+"/test")) | ||
| 315 | |||
| 316 | } | ||
| 317 | addhandler isafwreport_handler | ||
| 318 | isafwreport_handler[eventmask] = "bb.event.BuildStarted" | ||
