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 where they should be in -dev or -dbg
12 # -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
22 # We play a special package function
24 PACKAGE_DEPENDS += "pax-utils-native desktop-file-utils-native"
25 #PACKAGE_DEPENDS += chrpath-native"
26 PACKAGEFUNCS += " do_package_qa "
30 # dictionary for elf headers
32 # feel free to add and correct.
34 # TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit?
35 def package_qa_get_machine_dict():
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),
68 "bfin": ( 106, 0, 0, True, True),
71 "arm" : (40, 0, 0, True, True),
72 "armeb" : (40, 0, 0, False, True),
74 "linux-uclibcgnueabi" : {
75 "arm" : (40, 0, 0, True, True),
76 "armeb" : (40, 0, 0, False, True),
81 # factory for a class, embedded in a method
82 def package_qa_get_elf(path, bits32):
92 # possible values for EI_CLASS
97 # possible value for EI_VERSION
100 # possible values for EI_DATA
105 def my_assert(expectation, result):
106 if not expectation == result:
107 #print "'%x','%x'" % (ord(expectation), ord(result))
108 raise Exception("This does not work as expected")
109 my_assert = staticmethod(my_assert)
111 def __init__(self, name):
115 self.file = file(self.name, "r")
116 self.data = self.file.read(ELFFile.EI_NIDENT+4)
118 ELFFile.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
119 ELFFile.my_assert(self.data[0], chr(0x7f) )
120 ELFFile.my_assert(self.data[1], 'E')
121 ELFFile.my_assert(self.data[2], 'L')
122 ELFFile.my_assert(self.data[3], 'F')
124 ELFFile.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32)) # only 32 bits
126 ELFFile.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64)) # only 64 bits
127 ELFFile.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) )
129 self.sex = self.data[ELFFile.EI_DATA]
130 if self.sex == chr(ELFFile.ELFDATANONE):
131 raise Exception("self.sex == ELFDATANONE")
132 elif self.sex == chr(ELFFile.ELFDATA2LSB):
134 elif self.sex == chr(ELFFile.ELFDATA2MSB):
137 raise Exception("Unknown self.sex")
140 return ord(self.data[ELFFile.EI_OSABI])
142 def abiVersion(self):
143 return ord(self.data[ELFFile.EI_ABIVERSION])
145 def isLittleEndian(self):
146 return self.sex == "<"
148 def isBigEngian(self):
149 return self.sex == ">"
153 We know the sex stored in self.sex and we
157 (a,) = struct.unpack(self.sex+"H", self.data[18:20])
165 # Known Error classes
166 # 0 - non dev contains .so
167 # 1 - package contains a dangerous RPATH
168 # 2 - package depends on debug package
169 # 3 - non dbg contains .so
170 # 4 - wrong architecture
171 # 5 - .la contains installed=yes or reference to the workdir
172 # 6 - .pc contains reference to /usr/include or workdir
176 def package_qa_clean_path(path,d):
178 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
180 def package_qa_make_fatal_error(error_class, name, path,d):
182 decide if an error is fatal
184 TODO: Load a whitelist of known errors
191 def package_qa_write_error(error_class, name, path, d):
193 if not bb.data.getVar('QA_LOG', d):
197 "non dev contains .so",
198 "package contains RPATH",
199 "package depends on debug package",
200 "non dbg contains .debug",
201 "wrong architecture",
202 "evil hides inside the .la",
203 "evil hides inside the .pc",
207 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
208 f = file( log_path, "a+")
209 print >> f, "%s, %s, %s" % (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
213 def package_qa_check_rpath(file,name,d):
215 Check for dangerous RPATHs
218 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
219 #chrpath = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'chrpath')
220 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
221 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
222 if not os.path.exists(scanelf):
223 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
224 #if not os.path.exists(chrpath):
225 # bb.fatal("Can not fix RPATH, chrpath (part of chrpath-native) not found")
226 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
227 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
229 #bb.note("%s -B -F%%r#F %s" % (scanelf,file))
230 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
231 txt = output.readline().split()
232 #bb.note("???%s???" % bad_dir_test)
234 #bb.note("===%s===" % line)
236 package_qa_write_error( 1, name, file, d)
237 bb.error("QA Issue package %s contains bad RPATH %s in file %s" % (name, line, file))
238 #bb.note("Fixing RPATH for you in %s" % file)
239 #os.popen("%s -r /lib %s" % (chrpath,file))
243 def package_qa_check_devdbg(path, name,d):
245 Check for debug remains inside the binary or
246 non dev packages containing
252 if not "-dev" in name:
253 if path[-3:] == ".so" and os.path.islink(path):
254 package_qa_write_error( 0, name, path, d )
255 bb.error("QA Issue: non -dev package contains symlink .so: %s path '%s'" % (name, package_qa_clean_path(path,d)))
256 if package_qa_make_fatal_error( 0, name, path, d ):
259 if not "-dbg" in name:
261 package_qa_write_error( 3, name, path, d )
262 bb.error("QA Issue: non debug package contains .debug directory: %s path %s" % (name, package_qa_clean_path(path,d)))
263 if package_qa_make_fatal_error( 3, name, path, d ):
268 def package_qa_check_perm(path,name,d):
270 Check the permission of files
275 def package_qa_check_arch(path,name,d):
277 Check if archs are compatible
280 target_os = bb.data.getVar('TARGET_OS', d, True)
281 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
283 # FIXME: Cross package confuse this check, so just skip them
284 if bb.data.inherits_class('cross', d) or bb.data.inherits_class('sdk', d):
287 # avoid following links to /usr/bin (e.g. on udev builds)
288 # we will check the files pointed to anyway...
289 if os.path.islink(path):
292 #if this will throw an exception, then fix the dict above
293 (machine, osabi, abiversion, littleendian, bits32) = package_qa_get_machine_dict()[target_os][target_arch]
294 elf = package_qa_get_elf(path, bits32)
298 # just for debbugging to check the parser, remove once convinced...
301 if not machine == elf.machine():
302 bb.error("Architecture did not match (%d to %d) on %s" %(machine, elf.machine(), package_qa_clean_path(path,d)))
303 return not package_qa_make_fatal_error( 4, name, path, d )
304 elif not littleendian == elf.isLittleEndian():
305 bb.error("Endiannes did not match (%d to %d) on %s" % (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d)))
306 return not package_qa_make_fatal_error( 4, name, path, d )
310 def package_qa_check_pcla(path,name,d):
312 .pc and .la files should not point to the WORKDIR
318 def package_qa_check_desktop(path, name, d):
320 Run all desktop files through desktop-file-validate.
323 if path.endswith(".desktop"):
324 validate = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True), 'desktop-file-validate')
325 output = os.popen("%s %s" % (validate, path))
326 # This only produces output on errors
331 def package_qa_check_staged(path,d):
333 Check staged la and pc files for sanity
334 -e.g. installed being false
336 As this is run after every stage we should be able
337 to find the one responsible for the errors easily even
338 if we look at every .pc and .la file
343 workdir = os.path.join(bb.data.getVar('TMPDIR', d, True), "work")
345 if bb.data.inherits_class("native", d):
346 installed = "installed=no"
348 installed = "installed=yes"
350 # find all .la and .pc files
352 # and check for stuff that looks wrong
353 for root, dirs, files in os.walk(path):
355 path = os.path.join(root,file)
356 if file[-2:] == "la":
357 file_content = open(path).read()
358 if installed in file_content or workdir in file_content:
359 bb.error("QA issue: %s failed sanity test (reference to workdir or installed)" % file )
360 if package_qa_make_fatal_error( 5, "staging", path, d):
362 elif file[-2:] == "pc":
363 file_content = open(path).read()
364 if workdir in file_content:
365 bb.error("QA issue: %s failed sanity test (reference to workdir)" % file )
366 if package_qa_make_fatal_error( 6, "staging", path, d):
371 # Walk over all files in a directory and call func
372 def package_qa_walk(path, funcs, package,d):
376 for root, dirs, files in os.walk(path):
378 path = os.path.join(root,file)
380 if not func(path, package,d):
386 def package_qa_check_rdepends(pkg, workdir, d):
389 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
390 # Copied from package_ipk.bbclass
391 # boiler plate to update the data
392 localdata = bb.data.createCopy(d)
393 root = "%s/install/%s" % (workdir, pkg)
395 bb.data.setVar('ROOT', '', localdata)
396 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
397 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
400 bb.data.setVar('PKG', pkgname, localdata)
402 overrides = bb.data.getVar('OVERRIDES', localdata)
404 raise bb.build.FuncFailed('OVERRIDES not defined')
405 overrides = bb.data.expand(overrides, localdata)
406 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
408 bb.data.update_data(localdata)
410 # Now check the RDEPENDS
411 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
414 # Now do the sanity check!!!
415 for rdepend in rdepends:
416 if "-dbg" in rdepend:
417 package_qa_write_error( 2, pkgname, rdepend, d )
418 bb.error("QA issue: %s rdepends on %s" % (pkgname,rdepend))
419 if package_qa_make_fatal_error( 2, pkgname, rdepend, d ):
424 # The PACKAGE FUNC to scan each package
425 python do_package_qa () {
426 bb.note("DO PACKAGE QA")
427 workdir = bb.data.getVar('WORKDIR', d, True)
428 packages = bb.data.getVar('PACKAGES',d, True)
430 # no packages should be scanned
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.note("Checking Package: %s" % package)
442 path = "%s/install/%s" % (workdir, package)
443 if not package_qa_walk(path, [package_qa_check_rpath, package_qa_check_devdbg, package_qa_check_perm, package_qa_check_arch, package_qa_check_desktop], package, d):
445 if not package_qa_check_rdepends(package, workdir, 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.note("DONE with PACKAGE QA")
454 # The Staging Func, to check all staging
455 addtask qa_staging after do_populate_staging before do_build
456 python do_qa_staging() {
457 bb.note("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() {
466 bb.note("Checking sanity of the config.log file")
468 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
469 if "config.log" in files:
470 if os.system("grep 'CROSS COMPILE Badness:' %s > /dev/null" % (os.path.join(root,"config.log"))) == 0:
471 bb.fatal("This autoconf log indicates errors, it looked at host includes. Rerun configure task after fixing this. Path was '%s'" % root)