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 "mips64": ( 8, 0, 0, False, False),
52 "mips64el": ( 8, 0, 0, True, False),
53 "nios2": ( 113, 0, 0, True, True),
54 "powerpc": ( 20, 0, 0, False, True),
55 "s390": ( 22, 0, 0, False, True),
56 "sh4": ( 42, 0, 0, True, True),
57 "sparc": ( 2, 0, 0, False, True),
60 "arm" : ( 40, 97, 0, True, True),
61 "armeb": ( 40, 97, 0, False, True),
62 "avr32": ( 6317, 0, 0, False, True),
63 "i386": ( 3, 0, 0, True, True),
64 "i486": ( 3, 0, 0, True, True),
65 "i586": ( 3, 0, 0, True, True),
66 "i686": ( 3, 0, 0, True, True),
67 "x86_64": ( 62, 0, 0, True, False),
68 "mips": ( 8, 0, 0, False, True),
69 "mipsel": ( 8, 0, 0, True, True),
70 "mips64": ( 8, 0, 0, False, False),
71 "mips64el": ( 8, 0, 0, True, False),
72 "nios2": ( 113, 0, 0, True, True),
73 "powerpc": ( 20, 0, 0, False, True),
74 "sh4": ( 42, 0, 0, True, True),
77 "bfin": ( 106, 0, 0, True, True),
80 "arm" : ( 40, 0, 0, True, True),
81 "armeb" : ( 40, 0, 0, False, True),
83 "linux-uclibceabi" : {
84 "arm" : ( 40, 0, 0, True, True),
85 "armeb" : ( 40, 0, 0, False, True),
88 "powerpc": ( 20, 0, 0, False, True),
91 "powerpc": ( 20, 0, 0, False, True),
98 # 0 - non dev contains .so
99 # 1 - package contains a dangerous RPATH
100 # 2 - package depends on debug package
101 # 3 - non dbg contains .so
102 # 4 - wrong architecture
103 # 5 - .la contains installed=yes or reference to the workdir
104 # 6 - .pc contains reference to /usr/include or workdir
105 # 7 - the desktop file is not valid
106 # 8 - .la contains reference to the workdir
107 # 9 - LDFLAGS ignored
109 def package_qa_clean_path(path,d):
110 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
111 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
113 def package_qa_make_fatal_error(error_class, name, path,d):
115 decide if an error is fatal
117 TODO: Load a whitelist of known errors
119 return not error_class in [0, 5, 7]
121 def package_qa_write_error(error_class, name, path, d):
125 if not bb.data.getVar('QA_LOG', d):
126 bb.note("a QA error occured but will not be logged because QA_LOG is not set")
130 "non dev contains .so",
131 "package contains RPATH",
132 "package depends on debug package",
133 "non dbg contains .debug",
134 "wrong architecture",
135 "evil hides inside the .la",
136 "evil hides inside the .pc",
137 "the desktop file is not valid",
138 ".la contains reference to the workdir",
142 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
143 f = file( log_path, "a+")
144 print >> f, "%s, %s, %s" % \
145 (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
148 def package_qa_handle_error(error_class, error_msg, name, path, d):
149 bb.error("QA Issue with %s: %s" % (name, error_msg))
150 package_qa_write_error(error_class, name, path, d)
151 return not package_qa_make_fatal_error(error_class, name, path, d)
153 def package_qa_check_rpath(file,name,d, elf):
155 Check for dangerous RPATHs
162 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
163 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
164 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
165 if not os.path.exists(scanelf):
166 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
168 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
169 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
171 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
172 txt = output.readline().split()
175 error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
176 sane = package_qa_handle_error(1, error_msg, name, file, d)
180 def package_qa_check_dev(path, name,d, elf):
182 Check for ".so" library symlinks in non-dev packages
187 # SDK packages are special.
188 for s in ['sdk', 'canadian-sdk']:
189 if bb.data.inherits_class(s, d):
192 if not name.endswith("-dev") and path.endswith(".so") and os.path.islink(path):
193 error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
194 (name, package_qa_clean_path(path,d))
195 sane = package_qa_handle_error(0, error_msg, name, path, d)
199 def package_qa_check_dbg(path, name,d, elf):
201 Check for ".debug" files or directories outside of the dbg package
206 if not "-dbg" in name:
207 if '.debug' in path.split(os.path.sep):
208 error_msg = "non debug package contains .debug directory: %s path %s" % \
209 (name, package_qa_clean_path(path,d))
210 sane = package_qa_handle_error(3, error_msg, name, path, d)
214 def package_qa_check_perm(path,name,d, elf):
216 Check the permission of files
221 def package_qa_check_arch(path,name,d, elf):
223 Check if archs are compatible
229 target_os = bb.data.getVar('TARGET_OS', d, True)
230 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
232 # FIXME: Cross package confuse this check, so just skip them
233 for s in ['cross', 'sdk', 'canadian-cross', 'canadian-sdk']:
234 if bb.data.inherits_class(s, d):
237 # avoid following links to /usr/bin (e.g. on udev builds)
238 # we will check the files pointed to anyway...
239 if os.path.islink(path):
242 #if this will throw an exception, then fix the dict above
243 (machine, osabi, abiversion, littleendian, bits32) \
244 = package_qa_get_machine_dict()[target_os][target_arch]
246 # Check the architecture and endiannes of the binary
247 if not machine == elf.machine():
248 error_msg = "Architecture did not match (%d to %d) on %s" % \
249 (machine, elf.machine(), package_qa_clean_path(path,d))
250 sane = package_qa_handle_error(4, error_msg, name, path, d)
251 elif not littleendian == elf.isLittleEndian():
252 error_msg = "Endiannes did not match (%d to %d) on %s" % \
253 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
254 sane = package_qa_handle_error(4, error_msg, name, path, d)
258 def package_qa_check_desktop(path, name, d, elf):
260 Run all desktop files through desktop-file-validate.
263 env_path = bb.data.getVar('PATH', d, True)
265 if path.endswith(".desktop"):
266 output = os.popen("PATH=%s desktop-file-validate %s" % (env_path, path))
267 # This only produces output on errors
269 sane = package_qa_handle_error(7, l.strip(), name, path, d)
273 def package_qa_hash_style(path, name, d, elf):
275 Check if the binary has the right hash style...
281 if os.path.islink(path):
284 gnu_hash = "--hash-style=gnu" in bb.data.getVar('LDFLAGS', d, True)
286 gnu_hash = "--hash-style=both" in bb.data.getVar('LDFLAGS', d, True)
290 objdump = bb.data.getVar('OBJDUMP', d, True)
291 env_path = bb.data.getVar('PATH', d, True)
295 # A bit hacky. We do not know if path is an elf binary or not
296 # we will search for 'NEEDED' or 'INIT' as this should be printed...
297 # and come before the HASH section (guess!!!) and works on split out
299 for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, path), "r"):
300 if "NEEDED" in line or "INIT" in line:
303 if "GNU_HASH" in line:
305 if "[mips32]" in line or "[mips64]" in line:
309 error_msg = "No GNU_HASH in the elf binary: '%s'" % path
310 return package_qa_handle_error(9, error_msg, name, path, d)
314 def package_qa_check_staged(path,d):
316 Check staged la and pc files for sanity
317 -e.g. installed being false
319 As this is run after every stage we should be able
320 to find the one responsible for the errors easily even
321 if we look at every .pc and .la file
325 tmpdir = bb.data.getVar('TMPDIR', d, True)
326 workdir = os.path.join(tmpdir, "work")
328 installed = "installed=yes"
329 iscrossnative = False
330 pkgconfigcheck = tmpdir
331 for s in ['cross', 'native', 'canadian-cross', 'canadian-native']:
332 if bb.data.inherits_class(s, d):
333 pkgconfigcheck = workdir
336 # find all .la and .pc files
338 # and check for stuff that looks wrong
339 for root, dirs, files in os.walk(path):
341 path = os.path.join(root,file)
342 if file.endswith(".la"):
343 file_content = open(path).read()
344 # Don't check installed status for native/cross packages
345 if not iscrossnative and bb.data.getVar('LIBTOOL_HAS_SYSROOT', d, True) is "no":
346 if installed in file_content:
347 error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
348 sane = package_qa_handle_error(5, error_msg, "staging", path, d)
349 if workdir in file_content:
350 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
351 sane = package_qa_handle_error(8, error_msg, "staging", path, d)
352 elif file.endswith(".pc"):
353 file_content = open(path).read()
354 if pkgconfigcheck in file_content:
355 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
356 sane = package_qa_handle_error(6, error_msg, "staging", path, d)
360 # Walk over all files in a directory and call func
361 def package_qa_walk(path, funcs, package,d):
364 #if this will throw an exception, then fix the dict above
365 target_os = bb.data.getVar('TARGET_OS', d, True)
366 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
367 (machine, osabi, abiversion, littleendian, bits32) \
368 = package_qa_get_machine_dict()[target_os][target_arch]
371 for root, dirs, files in os.walk(path):
373 path = os.path.join(root,file)
374 elf = oe.qa.ELFFile(path, bits32)
380 if not func(path, package,d, elf):
385 def package_qa_check_rdepends(pkg, pkgdest, d):
387 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
388 # Copied from package_ipk.bbclass
389 # boiler plate to update the data
390 localdata = bb.data.createCopy(d)
391 root = "%s/%s" % (pkgdest, pkg)
393 bb.data.setVar('ROOT', '', localdata)
394 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
395 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
398 bb.data.setVar('PKG', pkgname, localdata)
400 overrides = bb.data.getVar('OVERRIDES', localdata)
402 raise bb.build.FuncFailed('OVERRIDES not defined')
403 overrides = bb.data.expand(overrides, localdata)
404 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
406 bb.data.update_data(localdata)
408 # Now check the RDEPENDS
409 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
412 # Now do the sanity check!!!
413 for rdepend in rdepends:
414 if "-dbg" in rdepend:
415 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
416 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
420 # The PACKAGE FUNC to scan each package
421 python do_package_qa () {
422 bb.debug(2, "DO PACKAGE QA")
423 pkgdest = bb.data.getVar('PKGDEST', d, True)
424 packages = bb.data.getVar('PACKAGES',d, True)
426 # no packages should be scanned
430 checks = [package_qa_check_rpath, package_qa_check_dev,
431 package_qa_check_perm, package_qa_check_arch,
432 package_qa_check_desktop, package_qa_hash_style,
433 package_qa_check_dbg]
436 for package in packages.split():
437 if bb.data.getVar('INSANE_SKIP_' + package, d, True):
438 bb.note("package %s skipped" % package)
441 bb.debug(1, "Checking Package: %s" % package)
442 path = "%s/%s" % (pkgdest, package)
443 if not package_qa_walk(path, checks, package, d):
445 if not package_qa_check_rdepends(package, pkgdest, d):
446 rdepends_sane = False
448 if not walk_sane or not rdepends_sane:
449 bb.fatal("QA run found fatal errors. Please consider fixing them.")
450 bb.debug(2, "DONE with PACKAGE QA")
454 # The Staging Func, to check all staging
455 addtask qa_staging after do_populate_sysroot before do_package_stage
456 python do_qa_staging() {
457 bb.debug(2, "QA checking staging")
459 if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
460 bb.fatal("QA staging was broken by the package built above")
463 # Check broken config.log files
464 addtask qa_configure after do_configure before do_compile
465 python do_qa_configure() {
467 bb.debug(1, "Checking sanity of the config.log file")
468 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
471 statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
472 os.path.join(root,"config.log")
473 if "config.log" in files:
474 if os.system(statement) == 0:
475 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
476 Rerun configure task after fixing this. The path was '%s'""" % root)
478 if "configure.ac" in files:
479 configs.append(os.path.join(root,"configure.ac"))
480 if "configure.in" in files:
481 configs.append(os.path.join(root, "configure.in"))
483 if "gettext" not in bb.data.getVar('P', d, True):
484 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):
485 gt = "gettext-native"
486 elif bb.data.inherits_class('cross-canadian', d):
487 gt = "gettext-nativesdk"
490 deps = bb.utils.explode_deps(bb.data.getVar('DEPENDS', d, True) or "")
492 for config in configs:
493 gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config
494 if os.system(gnu) == 0:
495 bb.note("""Gettext required but not in DEPENDS for file %s.
496 Missing inherit gettext?""" % config)