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 "mipsel": ( 8, 0, 0, True, True),
61 "avr32": (6317, 0, 0, False, True),
64 "bfin": ( 106, 0, 0, True, True),
67 "arm" : (40, 0, 0, True, True),
68 "armeb" : (40, 0, 0, False, True),
70 "linux-uclibcgnueabi" : {
71 "arm" : (40, 0, 0, True, True),
72 "armeb" : (40, 0, 0, False, True),
77 # factory for a class, embedded in a method
78 def package_qa_get_elf(path, bits32):
88 # possible values for EI_CLASS
93 # possible value for EI_VERSION
96 # possible values for EI_DATA
101 def my_assert(expectation, result):
102 if not expectation == result:
103 #print "'%x','%x'" % (ord(expectation), ord(result))
104 raise Exception("This does not work as expected")
105 my_assert = staticmethod(my_assert)
107 def __init__(self, name):
111 self.file = file(self.name, "r")
112 self.data = self.file.read(ELFFile.EI_NIDENT+4)
114 ELFFile.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
115 ELFFile.my_assert(self.data[0], chr(0x7f) )
116 ELFFile.my_assert(self.data[1], 'E')
117 ELFFile.my_assert(self.data[2], 'L')
118 ELFFile.my_assert(self.data[3], 'F')
120 ELFFile.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32)) # only 32 bits
122 ELFFile.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64)) # only 64 bits
123 ELFFile.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])
161 # Known Error classes
162 # 0 - non dev contains .so
163 # 1 - package contains a dangerous RPATH
164 # 2 - package depends on debug package
165 # 3 - non dbg contains .so
166 # 4 - wrong architecture
167 # 5 - .la contains installed=yes or reference to the workdir
168 # 6 - .pc contains reference to /usr/include or workdir
172 def package_qa_clean_path(path,d):
174 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
176 def package_qa_make_fatal_error(error_class, name, path,d):
178 decide if an error is fatal
180 TODO: Load a whitelist of known errors
187 def package_qa_write_error(error_class, name, path, d):
189 if not bb.data.getVar('QA_LOG', d):
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",
203 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
204 f = file( log_path, "a+")
205 print >> f, "%s, %s, %s" % (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
209 def package_qa_check_rpath(file,name,d):
211 Check for dangerous RPATHs
214 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
215 #chrpath = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'chrpath')
216 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
217 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
218 if not os.path.exists(scanelf):
219 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
220 #if not os.path.exists(chrpath):
221 # bb.fatal("Can not fix RPATH, chrpath (part of chrpath-native) not found")
222 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
223 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
225 #bb.note("%s -B -F%%r#F %s" % (scanelf,file))
226 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
227 txt = output.readline().split()
228 #bb.note("???%s???" % bad_dir_test)
230 #bb.note("===%s===" % line)
232 package_qa_write_error( 1, name, file, d)
233 bb.error("QA Issue package %s contains bad RPATH %s in file %s" % (name, line, file))
234 #bb.note("Fixing RPATH for you in %s" % file)
235 #os.popen("%s -r /lib %s" % (chrpath,file))
239 def package_qa_check_devdbg(path, name,d):
241 Check for debug remains inside the binary or
242 non dev packages containing
248 if not "-dev" in name:
249 if path[-3:] == ".so" and os.path.islink(path):
250 package_qa_write_error( 0, name, path, d )
251 bb.error("QA Issue: non -dev package contains symlink .so: %s path '%s'" % (name, package_qa_clean_path(path,d)))
252 if package_qa_make_fatal_error( 0, name, path, d ):
255 if not "-dbg" in name:
257 package_qa_write_error( 3, name, path, d )
258 bb.error("QA Issue: non debug package contains .debug directory: %s path %s" % (name, package_qa_clean_path(path,d)))
259 if package_qa_make_fatal_error( 3, name, path, d ):
264 def package_qa_check_perm(path,name,d):
266 Check the permission of files
271 def package_qa_check_arch(path,name,d):
273 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) = package_qa_get_machine_dict()[target_os][target_arch]
290 elf = package_qa_get_elf(path, bits32)
294 # just for debbugging to check the parser, remove once convinced...
297 if not machine == elf.machine():
298 bb.error("Architecture did not match (%d to %d) on %s" %(machine, elf.machine(), package_qa_clean_path(path,d)))
299 return not package_qa_make_fatal_error( 4, name, path, d )
300 elif not littleendian == elf.isLittleEndian():
301 bb.error("Endiannes did not match (%d to %d) on %s" % (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d)))
302 return not package_qa_make_fatal_error( 4, name, path, d )
306 def package_qa_check_pcla(path,name,d):
308 .pc and .la files should not point to the WORKDIR
314 def package_qa_check_desktop(path, name, d):
316 Run all desktop files through desktop-file-validate.
319 if path.endswith(".desktop"):
320 validate = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True), 'desktop-file-validate')
321 output = os.popen("%s %s" % (validate, path))
322 # This only produces output on errors
327 def package_qa_check_staged(path,d):
329 Check staged la and pc files for sanity
330 -e.g. installed being false
332 As this is run after every stage we should be able
333 to find the one responsible for the errors easily even
334 if we look at every .pc and .la file
339 workdir = os.path.join(bb.data.getVar('TMPDIR', d, True), "work")
341 if bb.data.inherits_class("native", d):
342 installed = "installed=no"
344 installed = "installed=yes"
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 or workdir in file_content:
355 bb.error("QA issue: %s failed sanity test (reference to workdir or installed)" % file )
356 if package_qa_make_fatal_error( 5, "staging", path, d):
358 elif file[-2:] == "pc":
359 file_content = open(path).read()
360 if workdir in file_content:
361 bb.error("QA issue: %s failed sanity test (reference to workdir)" % file )
362 if package_qa_make_fatal_error( 6, "staging", path, d):
367 # Walk over all files in a directory and call func
368 def package_qa_walk(path, funcs, package,d):
372 for root, dirs, files in os.walk(path):
374 path = os.path.join(root,file)
376 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 package_qa_write_error( 2, pkgname, rdepend, d )
414 bb.error("QA issue: %s rdepends on %s" % (pkgname,rdepend))
415 if package_qa_make_fatal_error( 2, pkgname, rdepend, d ):
420 # The PACKAGE FUNC to scan each package
421 python do_package_qa () {
422 bb.note("DO PACKAGE QA")
423 workdir = bb.data.getVar('WORKDIR', d, True)
424 packages = bb.data.getVar('PACKAGES',d, True)
426 # no packages should be scanned
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.note("Checking Package: %s" % package)
438 path = "%s/install/%s" % (workdir, package)
439 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):
441 if not package_qa_check_rdepends(package, workdir, 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.note("DONE with PACKAGE QA")
450 # The Staging Func, to check all staging
451 addtask qa_staging after do_populate_staging before do_build
452 python do_qa_staging() {
453 bb.note("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() {
462 bb.note("Checking sanity of the config.log file")
464 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
465 if "config.log" in files:
466 if os.system("grep 'CROSS COMPILE Badness:' %s > /dev/null" % (os.path.join(root,"config.log"))) == 0:
467 bb.fatal("This autoconf log indicates errors, it looked at host includes. Rerun configure task after fixing this. Path was '%s'" % root)