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, 97, 0, True, True),
36 "armeb": (40, 97, 0, False, True),
37 "powerpc": (20, 0, 0, False, True),
38 "i386": ( 3, 0, 0, True, True),
39 "i486": ( 3, 0, 0, True, True),
40 "i586": ( 3, 0, 0, True, True),
41 "i686": ( 3, 0, 0, True, True),
42 "x86_64": (62, 0, 0, True, False),
43 "ia64": (50, 0, 0, True, False),
44 "alpha": (36902, 0, 0, True, False),
45 "hppa": (15, 3, 0, False, True),
46 "m68k": ( 4, 0, 0, False, True),
47 "mips": ( 8, 0, 0, False, True),
48 "mipsel": ( 8, 0, 0, True, True),
49 "s390": (22, 0, 0, False, True),
50 "sh4": (42, 0, 0, True, True),
51 "sparc": ( 2, 0, 0, False, True),
54 "arm" : ( 40, 97, 0, True, True),
55 "armeb": ( 40, 97, 0, False, True),
56 "powerpc": ( 20, 0, 0, False, True),
57 "i386": ( 3, 0, 0, True, True),
58 "i486": ( 3, 0, 0, True, True),
59 "i586": ( 3, 0, 0, True, True),
60 "i686": ( 3, 0, 0, True, True),
61 "mipsel": ( 8, 0, 0, True, True),
62 "avr32": (6317, 0, 0, False, True),
65 "bfin": ( 106, 0, 0, True, True),
68 "arm" : (40, 0, 0, True, True),
69 "armeb" : (40, 0, 0, False, True),
71 "linux-uclibcgnueabi" : {
72 "arm" : (40, 0, 0, True, True),
73 "armeb" : (40, 0, 0, False, True),
78 # factory for a class, embedded in a method
79 def package_qa_get_elf(path, bits32):
89 # possible values for EI_CLASS
94 # possible value for EI_VERSION
97 # possible values for EI_DATA
102 def my_assert(self, expectation, result):
103 if not expectation == result:
104 #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
105 raise Exception("This does not work as expected")
107 def __init__(self, name):
111 self.file = file(self.name, "r")
112 self.data = self.file.read(ELFFile.EI_NIDENT+4)
114 self.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
115 self.my_assert(self.data[0], chr(0x7f) )
116 self.my_assert(self.data[1], 'E')
117 self.my_assert(self.data[2], 'L')
118 self.my_assert(self.data[3], 'F')
120 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32))
122 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64))
123 self.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) )
125 self.sex = self.data[ELFFile.EI_DATA]
126 if self.sex == chr(ELFFile.ELFDATANONE):
127 raise Exception("self.sex == ELFDATANONE")
128 elif self.sex == chr(ELFFile.ELFDATA2LSB):
130 elif self.sex == chr(ELFFile.ELFDATA2MSB):
133 raise Exception("Unknown self.sex")
136 return ord(self.data[ELFFile.EI_OSABI])
138 def abiVersion(self):
139 return ord(self.data[ELFFile.EI_ABIVERSION])
141 def isLittleEndian(self):
142 return self.sex == "<"
144 def isBigEngian(self):
145 return self.sex == ">"
149 We know the sex stored in self.sex and we
153 (a,) = struct.unpack(self.sex+"H", self.data[18:20])
159 # Known Error classes
160 # 0 - non dev contains .so
161 # 1 - package contains a dangerous RPATH
162 # 2 - package depends on debug package
163 # 3 - non dbg contains .so
164 # 4 - wrong architecture
165 # 5 - .la contains installed=yes
166 # 6 - .pc contains reference to /usr/include or workdir
167 # 7 - the desktop file is not valid
168 # 8 - .la contains reference to the workdir
170 def package_qa_clean_path(path,d):
171 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
173 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
175 def package_qa_make_fatal_error(error_class, name, path,d):
177 decide if an error is fatal
179 TODO: Load a whitelist of known errors
181 return not error_class in [0, 5, 7, 8]
183 def package_qa_write_error(error_class, name, path, d):
188 if not bb.data.getVar('QA_LOG', d):
189 bb.note("a QA error occured but will not be logged because QA_LOG is not set")
193 "non dev contains .so",
194 "package contains RPATH",
195 "package depends on debug package",
196 "non dbg contains .debug",
197 "wrong architecture",
198 "evil hides inside the .la",
199 "evil hides inside the .pc",
200 "the desktop file is not valid",
201 ".la contains reference to the workdir",
204 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
205 f = file( log_path, "a+")
206 print >> f, "%s, %s, %s" % \
207 (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
210 def package_qa_handle_error(error_class, error_msg, name, path, d):
212 bb.error("QA Issue: %s" % error_msg)
213 package_qa_write_error(error_class, name, path, d)
214 return not package_qa_make_fatal_error(error_class, name, path, d)
216 def package_qa_check_rpath(file,name,d):
218 Check for dangerous RPATHs
222 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
223 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
224 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
225 if not os.path.exists(scanelf):
226 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
228 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
229 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
231 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
232 txt = output.readline().split()
235 error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
236 sane = package_qa_handle_error(1, error_msg, name, file, d)
240 def package_qa_check_devdbg(path, name,d):
242 Check for debug remains inside the binary or
243 non dev packages containing
249 if not "-dev" in name:
250 if path[-3:] == ".so" and os.path.islink(path):
251 error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
252 (name, package_qa_clean_path(path,d))
253 sane = package_qa_handle_error(0, error_msg, name, path, d)
255 if not "-dbg" in name:
257 error_msg = "non debug package contains .debug directory: %s path %s" % \
258 (name, package_qa_clean_path(path,d))
259 sane = package_qa_handle_error(3, error_msg, name, path, d)
263 def package_qa_check_perm(path,name,d):
265 Check the permission of files
270 def package_qa_check_arch(path,name,d):
272 Check if archs are compatible
276 target_os = bb.data.getVar('TARGET_OS', d, True)
277 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
279 # FIXME: Cross package confuse this check, so just skip them
280 if bb.data.inherits_class('cross', d) or bb.data.inherits_class('sdk', d):
283 # avoid following links to /usr/bin (e.g. on udev builds)
284 # we will check the files pointed to anyway...
285 if os.path.islink(path):
288 #if this will throw an exception, then fix the dict above
289 (machine, osabi, abiversion, littleendian, bits32) \
290 = package_qa_get_machine_dict()[target_os][target_arch]
291 elf = package_qa_get_elf(path, bits32)
297 # Check the architecture and endiannes of the binary
298 if not machine == elf.machine():
299 error_msg = "Architecture did not match (%d to %d) on %s" % \
300 (machine, elf.machine(), package_qa_clean_path(path,d))
301 sane = package_qa_handle_error(4, error_msg, name, path, d)
302 elif not littleendian == elf.isLittleEndian():
303 error_msg = "Endiannes did not match (%d to %d) on %s" % \
304 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
305 sane = package_qa_handle_error(4, error_msg, name, path, d)
309 def package_qa_check_desktop(path, name, d):
311 Run all desktop files through desktop-file-validate.
315 if path.endswith(".desktop"):
316 validate = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True), \
317 'desktop-file-validate')
318 output = os.popen("%s %s" % (validate, path))
320 sane = package_qa_handle_error(7, l.strip(), name, path, d)
324 def package_qa_check_staged(path,d):
326 Check staged la and pc files for sanity
327 -e.g. installed being false
329 As this is run after every stage we should be able
330 to find the one responsible for the errors easily even
331 if we look at every .pc and .la file
336 tmpdir = bb.data.getVar('TMPDIR', d, True)
337 workdir = os.path.join(tmpdir, "work")
339 if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
340 installed = "installed=no"
341 pkgconfigcheck = workdir
343 installed = "installed=yes"
344 pkgconfigcheck = tmpdir
346 # find all .la and .pc files
348 # and check for stuff that looks wrong
349 for root, dirs, files in os.walk(path):
351 path = os.path.join(root,file)
352 if file[-2:] == "la":
353 file_content = open(path).read()
354 if installed in file_content:
355 error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
356 sane = package_qa_handle_error(5, error_msg, "staging", path, d)
357 if workdir in file_content:
358 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
359 sane = package_qa_handle_error(8, error_msg, "staging", path, d)
360 elif file[-2:] == "pc":
361 file_content = open(path).read()
362 if pkgconfigcheck in file_content:
363 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
364 sane = package_qa_handle_error(6, error_msg, "staging", path, d)
368 # Walk over all files in a directory and call func
369 def package_qa_walk(path, funcs, package,d):
373 for root, dirs, files in os.walk(path):
375 path = os.path.join(root,file)
377 if not func(path, package,d):
382 def package_qa_check_rdepends(pkg, workdir, d):
385 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
386 # Copied from package_ipk.bbclass
387 # boiler plate to update the data
388 localdata = bb.data.createCopy(d)
389 root = "%s/install/%s" % (workdir, pkg)
391 bb.data.setVar('ROOT', '', localdata)
392 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
393 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
396 bb.data.setVar('PKG', pkgname, localdata)
398 overrides = bb.data.getVar('OVERRIDES', localdata)
400 raise bb.build.FuncFailed('OVERRIDES not defined')
401 overrides = bb.data.expand(overrides, localdata)
402 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
404 bb.data.update_data(localdata)
406 # Now check the RDEPENDS
407 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
410 # Now do the sanity check!!!
411 for rdepend in rdepends:
412 if "-dbg" in rdepend:
413 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
414 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
418 # The PACKAGE FUNC to scan each package
419 python do_package_qa () {
420 bb.note("DO PACKAGE QA")
421 workdir = bb.data.getVar('WORKDIR', d, True)
422 packages = bb.data.getVar('PACKAGES',d, True)
424 # no packages should be scanned
428 checks = [package_qa_check_rpath, package_qa_check_devdbg,
429 package_qa_check_perm, package_qa_check_arch,
430 package_qa_check_desktop]
433 for package in packages.split():
434 if bb.data.getVar('INSANE_SKIP_' + package, d, True):
435 bb.note("Package: %s (skipped)" % package)
438 bb.note("Checking Package: %s" % package)
439 path = "%s/install/%s" % (workdir, package)
440 if not package_qa_walk(path, checks, package, d):
442 if not package_qa_check_rdepends(package, workdir, d):
443 rdepends_sane = False
445 if not walk_sane or not rdepends_sane:
446 bb.fatal("QA run found fatal errors. Please consider fixing them.")
447 bb.note("DONE with PACKAGE QA")
451 # The Staging Func, to check all staging
452 addtask qa_staging after do_populate_staging before do_build
453 python do_qa_staging() {
454 bb.note("QA checking staging")
456 if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
457 bb.fatal("QA staging was broken by the package built above")
460 # Check broken config.log files
461 addtask qa_configure after do_configure before do_compile
462 python do_qa_configure() {
463 bb.note("Checking sanity of the config.log file")
465 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
466 statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
467 os.path.join(root,"config.log")
468 if "config.log" in files:
469 if os.system(statement) == 0:
470 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
471 Rerun configure task after fixing this. The path was '%s'""" % root)