1 # BB Class inspired by ebuild.sh
3 # This class will test files after installation for certain
4 # security issues and other kind of issues.
7 # -Check the ownership and permissions
8 # -Check the RUNTIME path for the $TMPDIR
9 # -Check if .la files wrongly point to workdir
10 # -Check if .pc files wrongly point to workdir
11 # -Check if packages contains .debug directories or .so files
12 # where they should be in -dev or -dbg
13 # -Check if config.log contains traces to broken autoconf tests
17 # We need to have the scanelf utility as soon as
18 # possible and this is contained within the pax-utils-native.
19 # The package.bbclass can help us here.
22 PACKAGE_DEPENDS += "pax-utils-native desktop-file-utils-native"
23 PACKAGEFUNCS += " do_package_qa "
27 # dictionary for elf headers
29 # feel free to add and correct.
31 # TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit?
32 def package_qa_get_machine_dict():
35 "arm" : ( 40, 0, 0, True, True),
38 "arm" : ( 40, 97, 0, True, True),
39 "armeb": ( 40, 97, 0, False, True),
40 "i386": ( 3, 0, 0, True, True),
41 "i486": ( 3, 0, 0, True, True),
42 "i586": ( 3, 0, 0, True, True),
43 "i686": ( 3, 0, 0, True, True),
44 "x86_64": ( 62, 0, 0, True, False),
45 "ia64": ( 50, 0, 0, True, False),
46 "alpha": (36902, 0, 0, True, False),
47 "hppa": ( 15, 3, 0, False, True),
48 "m68k": ( 4, 0, 0, False, True),
49 "mips": ( 8, 0, 0, False, True),
50 "mipsel": ( 8, 0, 0, True, True),
51 "nios2": ( 113, 0, 0, True, True),
52 "powerpc": ( 20, 0, 0, False, True),
53 "s390": ( 22, 0, 0, False, True),
54 "sh4": ( 42, 0, 0, True, True),
55 "sparc": ( 2, 0, 0, False, True),
58 "arm" : ( 40, 97, 0, True, True),
59 "armeb": ( 40, 97, 0, False, True),
60 "avr32": ( 6317, 0, 0, False, True),
61 "i386": ( 3, 0, 0, True, True),
62 "i486": ( 3, 0, 0, True, True),
63 "i586": ( 3, 0, 0, True, True),
64 "i686": ( 3, 0, 0, True, True),
65 "x86_64": ( 62, 0, 0, True, False),
66 "mips": ( 8, 0, 0, False, True),
67 "mipsel": ( 8, 0, 0, True, True),
68 "nios2": ( 113, 0, 0, True, True),
69 "powerpc": ( 20, 0, 0, False, True),
70 "sh4": ( 42, 0, 0, True, True),
73 "bfin": ( 106, 0, 0, True, True),
76 "arm" : ( 40, 0, 0, True, True),
77 "armeb" : ( 40, 0, 0, False, True),
79 "linux-uclibceabi" : {
80 "arm" : ( 40, 0, 0, True, True),
81 "armeb" : ( 40, 0, 0, False, True),
84 "powerpc": ( 20, 0, 0, False, True),
87 "powerpc": ( 20, 0, 0, False, True),
94 # 0 - non dev contains .so
95 # 1 - package contains a dangerous RPATH
96 # 2 - package depends on debug package
97 # 3 - non dbg contains .so
98 # 4 - wrong architecture
99 # 5 - .la contains installed=yes or reference to the workdir
100 # 6 - .pc contains reference to /usr/include or workdir
101 # 7 - the desktop file is not valid
102 # 8 - .la contains reference to the workdir
103 # 9 - LDFLAGS ignored
105 def package_qa_clean_path(path,d):
106 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
107 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
109 def package_qa_make_fatal_error(error_class, name, path,d):
111 decide if an error is fatal
113 TODO: Load a whitelist of known errors
115 return not error_class in [0, 5, 7]
117 def package_qa_write_error(error_class, name, path, d):
121 if not bb.data.getVar('QA_LOG', d):
122 bb.note("a QA error occured but will not be logged because QA_LOG is not set")
126 "non dev contains .so",
127 "package contains RPATH",
128 "package depends on debug package",
129 "non dbg contains .debug",
130 "wrong architecture",
131 "evil hides inside the .la",
132 "evil hides inside the .pc",
133 "the desktop file is not valid",
134 ".la contains reference to the workdir",
138 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
139 f = file( log_path, "a+")
140 print >> f, "%s, %s, %s" % \
141 (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
144 def package_qa_handle_error(error_class, error_msg, name, path, d):
145 bb.error("QA Issue with %s: %s" % (name, error_msg))
146 package_qa_write_error(error_class, name, path, d)
147 return not package_qa_make_fatal_error(error_class, name, path, d)
149 def package_qa_check_rpath(file,name,d, elf):
151 Check for dangerous RPATHs
158 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
159 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
160 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
161 if not os.path.exists(scanelf):
162 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
164 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
165 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
167 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
168 txt = output.readline().split()
171 error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
172 sane = package_qa_handle_error(1, error_msg, name, file, d)
176 def package_qa_check_dev(path, name,d, elf):
178 Check for ".so" library symlinks in non-dev packages
183 # SDK packages are special.
184 for s in ['sdk', 'canadian-sdk']:
185 if bb.data.inherits_class(s, d):
188 if not name.endswith("-dev") and path.endswith(".so") and os.path.islink(path):
189 error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
190 (name, package_qa_clean_path(path,d))
191 sane = package_qa_handle_error(0, error_msg, name, path, d)
195 def package_qa_check_dbg(path, name,d, elf):
197 Check for ".debug" files or directories outside of the dbg package
202 if not "-dbg" in name:
203 if '.debug' in path.split(os.path.sep):
204 error_msg = "non debug package contains .debug directory: %s path %s" % \
205 (name, package_qa_clean_path(path,d))
206 sane = package_qa_handle_error(3, error_msg, name, path, d)
210 def package_qa_check_perm(path,name,d, elf):
212 Check the permission of files
217 def package_qa_check_arch(path,name,d, elf):
219 Check if archs are compatible
225 target_os = bb.data.getVar('TARGET_OS', d, True)
226 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
228 # FIXME: Cross package confuse this check, so just skip them
229 for s in ['cross', 'sdk', 'canadian-cross', 'canadian-sdk']:
230 if bb.data.inherits_class(s, d):
233 # avoid following links to /usr/bin (e.g. on udev builds)
234 # we will check the files pointed to anyway...
235 if os.path.islink(path):
238 #if this will throw an exception, then fix the dict above
239 (machine, osabi, abiversion, littleendian, bits32) \
240 = package_qa_get_machine_dict()[target_os][target_arch]
242 # Check the architecture and endiannes of the binary
243 if not machine == elf.machine():
244 error_msg = "Architecture did not match (%d to %d) on %s" % \
245 (machine, elf.machine(), package_qa_clean_path(path,d))
246 sane = package_qa_handle_error(4, error_msg, name, path, d)
247 elif not littleendian == elf.isLittleEndian():
248 error_msg = "Endiannes did not match (%d to %d) on %s" % \
249 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
250 sane = package_qa_handle_error(4, error_msg, name, path, d)
254 def package_qa_check_desktop(path, name, d, elf):
256 Run all desktop files through desktop-file-validate.
259 env_path = bb.data.getVar('PATH', d, True)
261 if path.endswith(".desktop"):
262 output = os.popen("PATH=%s desktop-file-validate %s" % (env_path, path))
263 # This only produces output on errors
265 sane = package_qa_handle_error(7, l.strip(), name, path, d)
269 def package_qa_hash_style(path, name, d, elf):
271 Check if the binary has the right hash style...
277 if os.path.islink(path):
280 gnu_hash = "--hash-style=gnu" in bb.data.getVar('LDFLAGS', d, True)
282 gnu_hash = "--hash-style=both" in bb.data.getVar('LDFLAGS', d, True)
286 objdump = bb.data.getVar('OBJDUMP', d, True)
287 env_path = bb.data.getVar('PATH', d, True)
291 # A bit hacky. We do not know if path is an elf binary or not
292 # we will search for 'NEEDED' or 'INIT' as this should be printed...
293 # and come before the HASH section (guess!!!) and works on split out
295 for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, path), "r"):
296 if "NEEDED" in line or "INIT" in line:
299 if "GNU_HASH" in line:
301 if "[mips32]" in line or "[mips64]" in line:
305 error_msg = "No GNU_HASH in the elf binary: '%s'" % path
306 return package_qa_handle_error(9, error_msg, name, path, d)
310 def package_qa_check_staged(path,d):
312 Check staged la and pc files for sanity
313 -e.g. installed being false
315 As this is run after every stage we should be able
316 to find the one responsible for the errors easily even
317 if we look at every .pc and .la file
321 tmpdir = bb.data.getVar('TMPDIR', d, True)
322 workdir = os.path.join(tmpdir, "work")
324 installed = "installed=yes"
325 iscrossnative = False
326 pkgconfigcheck = tmpdir
327 for s in ['cross', 'native', 'canadian-cross', 'canadian-native']:
328 if bb.data.inherits_class(s, d):
329 pkgconfigcheck = workdir
332 # find all .la and .pc files
334 # and check for stuff that looks wrong
335 for root, dirs, files in os.walk(path):
337 path = os.path.join(root,file)
338 if file.endswith(".la"):
339 file_content = open(path).read()
340 # Don't check installed status for native/cross packages
341 if not iscrossnative:
342 if installed in file_content:
343 error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
344 sane = package_qa_handle_error(5, error_msg, "staging", path, d)
345 if workdir in file_content:
346 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
347 sane = package_qa_handle_error(8, error_msg, "staging", path, d)
348 elif file.endswith(".pc"):
349 file_content = open(path).read()
350 if pkgconfigcheck in file_content:
351 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
352 sane = package_qa_handle_error(6, error_msg, "staging", path, d)
356 # Walk over all files in a directory and call func
357 def package_qa_walk(path, funcs, package,d):
360 #if this will throw an exception, then fix the dict above
361 target_os = bb.data.getVar('TARGET_OS', d, True)
362 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
363 (machine, osabi, abiversion, littleendian, bits32) \
364 = package_qa_get_machine_dict()[target_os][target_arch]
367 for root, dirs, files in os.walk(path):
369 path = os.path.join(root,file)
370 elf = oe.qa.ELFFile(path, bits32)
376 if not func(path, package,d, elf):
381 def package_qa_check_rdepends(pkg, pkgdest, d):
383 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
384 # Copied from package_ipk.bbclass
385 # boiler plate to update the data
386 localdata = bb.data.createCopy(d)
387 root = "%s/%s" % (pkgdest, pkg)
389 bb.data.setVar('ROOT', '', localdata)
390 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
391 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
394 bb.data.setVar('PKG', pkgname, localdata)
396 overrides = bb.data.getVar('OVERRIDES', localdata)
398 raise bb.build.FuncFailed('OVERRIDES not defined')
399 overrides = bb.data.expand(overrides, localdata)
400 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
402 bb.data.update_data(localdata)
404 # Now check the RDEPENDS
405 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
408 # Now do the sanity check!!!
409 for rdepend in rdepends:
410 if "-dbg" in rdepend:
411 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
412 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
416 # The PACKAGE FUNC to scan each package
417 python do_package_qa () {
418 bb.debug(2, "DO PACKAGE QA")
419 pkgdest = bb.data.getVar('PKGDEST', d, True)
420 packages = bb.data.getVar('PACKAGES',d, True)
422 # no packages should be scanned
426 checks = [package_qa_check_rpath, package_qa_check_dev,
427 package_qa_check_perm, package_qa_check_arch,
428 package_qa_check_desktop, package_qa_hash_style,
429 package_qa_check_dbg]
432 for package in packages.split():
433 if bb.data.getVar('INSANE_SKIP_' + package, d, True):
434 bb.note("package %s skipped" % package)
437 bb.debug(1, "Checking Package: %s" % package)
438 path = "%s/%s" % (pkgdest, package)
439 if not package_qa_walk(path, checks, package, d):
441 if not package_qa_check_rdepends(package, pkgdest, d):
442 rdepends_sane = False
444 if not walk_sane or not rdepends_sane:
445 bb.fatal("QA run found fatal errors. Please consider fixing them.")
446 bb.debug(2, "DONE with PACKAGE QA")
450 # The Staging Func, to check all staging
451 addtask qa_staging after do_populate_sysroot before do_package_stage
452 python do_qa_staging() {
453 bb.debug(2, "QA checking staging")
455 if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
456 bb.fatal("QA staging was broken by the package built above")
459 # Check broken config.log files
460 addtask qa_configure after do_configure before do_compile
461 python do_qa_configure() {
463 bb.debug(1, "Checking sanity of the config.log file")
464 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
465 statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
466 os.path.join(root,"config.log")
467 if "config.log" in files:
468 if os.system(statement) == 0:
469 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
470 Rerun configure task after fixing this. The path was '%s'""" % root)
472 if "configure.ac" in files:
473 configs.append(os.path.join(root,"configure.ac"))
474 if "configure.in" in files:
475 configs.append(os.path.join(root, "configure.in"))
477 if "gettext" not in bb.data.getVar('P', d, True):
478 if bb.data.inherits_class('native', d) or bb.data.inherits_class('cross', d) or bb.data.inherits_class('crosssdk', d) or bb.data.inherits_class('nativesdk', d):
479 gt = "gettext-native"
480 elif bb.data.inherits_class('cross-canadian', d):
481 gt = "gettext-nativesdk"
484 deps = bb.utils.explode_deps(bb.data.getVar('DEPENDS', d, True) or "")
486 for config in configs:
487 gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config
488 if os.system(gnu) == 0:
489 bb.note("""Gettext required but not in DEPENDS for file %s.
490 Missing inherit gettext?""" % config)