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 "powerpc": (20, 0, 0, False, True),
41 "i386": ( 3, 0, 0, True, True),
42 "i486": ( 3, 0, 0, True, True),
43 "i586": ( 3, 0, 0, True, True),
44 "i686": ( 3, 0, 0, True, True),
45 "x86_64": (62, 0, 0, True, False),
46 "ia64": (50, 0, 0, True, False),
47 "alpha": (36902, 0, 0, True, False),
48 "hppa": (15, 3, 0, False, True),
49 "m68k": ( 4, 0, 0, False, True),
50 "mips": ( 8, 0, 0, False, True),
51 "mipsel": ( 8, 0, 0, True, True),
52 "s390": (22, 0, 0, False, True),
53 "sh4": (42, 0, 0, True, True),
54 "sparc": ( 2, 0, 0, False, True),
57 "arm" : ( 40, 97, 0, True, True),
58 "armeb": ( 40, 97, 0, False, True),
59 "powerpc": ( 20, 0, 0, False, True),
60 "i386": ( 3, 0, 0, True, True),
61 "i486": ( 3, 0, 0, True, True),
62 "i586": ( 3, 0, 0, True, True),
63 "i686": ( 3, 0, 0, True, True),
64 "mipsel": ( 8, 0, 0, True, True),
65 "avr32": (6317, 0, 0, False, True),
66 "sh4": (42, 0, 0, True, True),
70 "bfin": ( 106, 0, 0, True, True),
73 "arm" : (40, 0, 0, True, True),
74 "armeb" : (40, 0, 0, False, True),
76 "linux-uclibcgnueabi" : {
77 "arm" : (40, 0, 0, True, True),
78 "armeb" : (40, 0, 0, False, True),
81 "powerpc": (20, 0, 0, False, True),
86 # factory for a class, embedded in a method
87 def package_qa_get_elf(path, bits32):
97 # possible values for EI_CLASS
102 # possible value for EI_VERSION
105 # possible values for EI_DATA
110 def my_assert(self, expectation, result):
111 if not expectation == result:
112 #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
113 raise Exception("This does not work as expected")
115 def __init__(self, name):
119 self.file = file(self.name, "r")
120 self.data = self.file.read(ELFFile.EI_NIDENT+4)
122 self.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
123 self.my_assert(self.data[0], chr(0x7f) )
124 self.my_assert(self.data[1], 'E')
125 self.my_assert(self.data[2], 'L')
126 self.my_assert(self.data[3], 'F')
128 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32))
130 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64))
131 self.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) )
133 self.sex = self.data[ELFFile.EI_DATA]
134 if self.sex == chr(ELFFile.ELFDATANONE):
135 raise Exception("self.sex == ELFDATANONE")
136 elif self.sex == chr(ELFFile.ELFDATA2LSB):
138 elif self.sex == chr(ELFFile.ELFDATA2MSB):
141 raise Exception("Unknown self.sex")
144 return ord(self.data[ELFFile.EI_OSABI])
146 def abiVersion(self):
147 return ord(self.data[ELFFile.EI_ABIVERSION])
149 def isLittleEndian(self):
150 return self.sex == "<"
152 def isBigEngian(self):
153 return self.sex == ">"
157 We know the sex stored in self.sex and we
161 (a,) = struct.unpack(self.sex+"H", self.data[18:20])
167 # Known Error classes
168 # 0 - non dev contains .so
169 # 1 - package contains a dangerous RPATH
170 # 2 - package depends on debug package
171 # 3 - non dbg contains .so
172 # 4 - wrong architecture
173 # 5 - .la contains installed=yes or reference to the workdir
174 # 6 - .pc contains reference to /usr/include or workdir
175 # 7 - the desktop file is not valid
176 # 8 - .la contains reference to the workdir
177 # 9 - LDFLAGS ignored
179 def package_qa_clean_path(path,d):
180 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
182 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
184 def package_qa_make_fatal_error(error_class, name, path,d):
186 decide if an error is fatal
188 TODO: Load a whitelist of known errors
190 return not error_class in [0, 5, 7]
192 def package_qa_write_error(error_class, name, path, d):
197 if not bb.data.getVar('QA_LOG', d):
198 bb.note("a QA error occured but will not be logged because QA_LOG is not set")
202 "non dev contains .so",
203 "package contains RPATH",
204 "package depends on debug package",
205 "non dbg contains .debug",
206 "wrong architecture",
207 "evil hides inside the .la",
208 "evil hides inside the .pc",
209 "the desktop file is not valid",
210 ".la contains reference to the workdir",
214 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
215 f = file( log_path, "a+")
216 print >> f, "%s, %s, %s" % \
217 (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
220 def package_qa_handle_error(error_class, error_msg, name, path, d):
222 bb.error("QA Issue: %s" % error_msg)
223 package_qa_write_error(error_class, name, path, d)
224 return not package_qa_make_fatal_error(error_class, name, path, d)
226 def package_qa_check_rpath(file,name,d, elf):
228 Check for dangerous RPATHs
235 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
236 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
237 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
238 if not os.path.exists(scanelf):
239 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
241 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
242 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
244 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
245 txt = output.readline().split()
248 error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
249 sane = package_qa_handle_error(1, error_msg, name, file, d)
253 def package_qa_check_devdbg(path, name,d, elf):
255 Check for debug remains inside the binary or
256 non dev packages containing
262 if not "-dev" in name:
263 if path[-3:] == ".so" and os.path.islink(path):
264 error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
265 (name, package_qa_clean_path(path,d))
266 sane = package_qa_handle_error(0, error_msg, name, path, d)
268 if not "-dbg" in name:
270 error_msg = "non debug package contains .debug directory: %s path %s" % \
271 (name, package_qa_clean_path(path,d))
272 sane = package_qa_handle_error(3, error_msg, name, path, d)
276 def package_qa_check_perm(path,name,d, elf):
278 Check the permission of files
283 def package_qa_check_arch(path,name,d, elf):
285 Check if archs are compatible
292 target_os = bb.data.getVar('TARGET_OS', d, True)
293 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
295 # FIXME: Cross package confuse this check, so just skip them
296 for s in ['cross', 'sdk', 'canadian-cross', 'canadian-sdk']:
297 if bb.data.inherits_class(s, d):
300 # avoid following links to /usr/bin (e.g. on udev builds)
301 # we will check the files pointed to anyway...
302 if os.path.islink(path):
305 #if this will throw an exception, then fix the dict above
306 (machine, osabi, abiversion, littleendian, bits32) \
307 = package_qa_get_machine_dict()[target_os][target_arch]
309 # Check the architecture and endiannes of the binary
310 if not machine == elf.machine():
311 error_msg = "Architecture did not match (%d to %d) on %s" % \
312 (machine, elf.machine(), package_qa_clean_path(path,d))
313 sane = package_qa_handle_error(4, error_msg, name, path, d)
314 elif not littleendian == elf.isLittleEndian():
315 error_msg = "Endiannes did not match (%d to %d) on %s" % \
316 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
317 sane = package_qa_handle_error(4, error_msg, name, path, d)
321 def package_qa_check_desktop(path, name, d, elf):
323 Run all desktop files through desktop-file-validate.
327 if path.endswith(".desktop"):
328 output = os.popen("desktop-file-validate %s" % path)
329 # This only produces output on errors
331 sane = package_qa_handle_error(7, l.strip(), name, path, d)
335 def package_qa_hash_style(path, name, d, elf):
337 Check if the binary has the right hash style...
344 if os.path.islink(path):
347 gnu_hash = "--hash-style=gnu" in bb.data.getVar('LDFLAGS', d, True)
349 gnu_hash = "--hash-style=both" in bb.data.getVar('LDFLAGS', d, True)
353 objdump = bb.data.getVar('OBJDUMP', d, True)
354 env_path = bb.data.getVar('PATH', d, True)
358 # A bit hacky. We do not know if path is an elf binary or not
359 # we will search for 'NEEDED' or 'INIT' as this should be printed...
360 # and come before the HASH section (guess!!!) and works on split out
362 for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, path), "r"):
363 if "NEEDED" in line or "INIT" in line:
366 if "GNU_HASH" in line:
368 if "[mips32]" in line or "[mips64]" in line:
372 error_msg = "No GNU_HASH in the elf binary: '%s'" % path
373 return package_qa_handle_error(9, error_msg, name, path, d)
377 def package_qa_check_staged(path,d):
379 Check staged la and pc files for sanity
380 -e.g. installed being false
382 As this is run after every stage we should be able
383 to find the one responsible for the errors easily even
384 if we look at every .pc and .la file
389 tmpdir = bb.data.getVar('TMPDIR', d, True)
390 workdir = os.path.join(tmpdir, "work")
392 installed = "installed=yes"
393 iscrossnative = False
394 pkgconfigcheck = tmpdir
395 for s in ['cross', 'native', 'canadian-cross', 'canadian-native']:
396 if bb.data.inherits_class(s, d):
397 pkgconfigcheck = workdir
400 # find all .la and .pc files
402 # and check for stuff that looks wrong
403 for root, dirs, files in os.walk(path):
405 path = os.path.join(root,file)
406 if file[-2:] == "la":
407 file_content = open(path).read()
408 # Don't check installed status for native/cross packages
409 if not iscrossnative:
410 if installed in file_content:
411 error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
412 sane = package_qa_handle_error(5, error_msg, "staging", path, d)
413 if workdir in file_content:
414 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
415 sane = package_qa_handle_error(8, error_msg, "staging", path, d)
416 elif file[-2:] == "pc":
417 file_content = open(path).read()
418 if pkgconfigcheck in file_content:
419 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
420 sane = package_qa_handle_error(6, error_msg, "staging", path, d)
424 # Walk over all files in a directory and call func
425 def package_qa_walk(path, funcs, package,d):
429 #if this will throw an exception, then fix the dict above
430 target_os = bb.data.getVar('TARGET_OS', d, True)
431 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
432 (machine, osabi, abiversion, littleendian, bits32) \
433 = package_qa_get_machine_dict()[target_os][target_arch]
435 for root, dirs, files in os.walk(path):
437 path = os.path.join(root,file)
438 elf = package_qa_get_elf(path, bits32)
444 if not func(path, package,d, elf):
449 def package_qa_check_rdepends(pkg, workdir, d):
452 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
453 # Copied from package_ipk.bbclass
454 # boiler plate to update the data
455 localdata = bb.data.createCopy(d)
456 root = "%s/install/%s" % (workdir, pkg)
458 bb.data.setVar('ROOT', '', localdata)
459 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
460 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
463 bb.data.setVar('PKG', pkgname, localdata)
465 overrides = bb.data.getVar('OVERRIDES', localdata)
467 raise bb.build.FuncFailed('OVERRIDES not defined')
468 overrides = bb.data.expand(overrides, localdata)
469 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
471 bb.data.update_data(localdata)
473 # Now check the RDEPENDS
474 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
477 # Now do the sanity check!!!
478 for rdepend in rdepends:
479 if "-dbg" in rdepend:
480 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
481 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
485 # The PACKAGE FUNC to scan each package
486 python do_package_qa () {
488 bb.note("DO PACKAGE QA")
489 workdir = bb.data.getVar('WORKDIR', d, True)
490 packages = bb.data.getVar('PACKAGES',d, True)
492 # no packages should be scanned
496 checks = [package_qa_check_rpath, package_qa_check_devdbg,
497 package_qa_check_perm, package_qa_check_arch,
498 package_qa_check_desktop, package_qa_hash_style]
501 for package in packages.split():
502 if bb.data.getVar('INSANE_SKIP_' + package, d, True):
503 bb.note("Package: %s (skipped)" % package)
506 bb.note("Checking Package: %s" % package)
507 path = "%s/install/%s" % (workdir, package)
508 if not package_qa_walk(path, checks, package, d):
510 if not package_qa_check_rdepends(package, workdir, d):
511 rdepends_sane = False
513 if not walk_sane or not rdepends_sane:
514 bb.fatal("QA run found fatal errors. Please consider fixing them.")
515 bb.note("DONE with PACKAGE QA")
519 # The Staging Func, to check all staging
520 addtask qa_staging after do_populate_staging before do_build
521 python do_qa_staging() {
522 bb.note("QA checking staging")
524 if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
525 bb.fatal("QA staging was broken by the package built above")
528 # Check broken config.log files
529 addtask qa_configure after do_configure before do_compile
530 python do_qa_configure() {
531 bb.note("Checking sanity of the config.log file")
533 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
534 statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
535 os.path.join(root,"config.log")
536 if "config.log" in files:
537 if os.system(statement) == 0:
538 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
539 Rerun configure task after fixing this. The path was '%s'""" % root)