Why not just check the permissions on disk, from a rootfs postprocess command? That wouldn’t be specific to tar, but would be just as effective, since those run under pseudo.
On Fri, Jul 7, 2017 at 12:36 PM, Haris Okanovic <haris.okano...@ni.com> wrote: > An IMAGE_POSTPROCESS_COMMAND verifying image tarballs have symbolic and > numeric ownership attributes which match the enclosed shadow database: > * uid and uname of each file is present in /etc/passwd > * gid and gname of each file is present in /etc/group > * ids and names are consistent between tar metadata and shadow database > > In other words, this test ensure there aren't any ownership surprises > when a filesystem created from tarball is booted. It's particularly > useful in cases where artifacts built outside of OE are included in > images -- E.g. packages from an external feed. > > Testing: Verified core-image-base still builds > > Patch: https://github.com/harisokanovic/openembedded- > core/commits/dev/hokanovi/image-ownership-sanity-check > > Signed-off-by: Haris Okanovic <haris.okano...@ni.com> > --- > meta/classes/image.bbclass | 25 +++++++++- > meta/lib/oe/image.py | 114 ++++++++++++++++++++++++++++++ > +++++++++++++++ > 2 files changed, 138 insertions(+), 1 deletion(-) > create mode 100644 meta/lib/oe/image.py > > diff --git a/meta/classes/image.bbclass b/meta/classes/image.bbclass > index 6e30b96745..7b11291a82 100644 > --- a/meta/classes/image.bbclass > +++ b/meta/classes/image.bbclass > @@ -192,7 +192,30 @@ python () { > IMAGE_CLASSES += "image_types" > inherit ${IMAGE_CLASSES} > > -IMAGE_POSTPROCESS_COMMAND ?= "" > +python check_image_file_ownership () { > + import sys, os, oe.image > + > + imgTypes = d.getVar("IMAGE_FSTYPES", True).split() > + tarImgTypes = [ x for x in imgTypes if "tar" in x ] > + > + # bail if there isn't a tar image in this build > + # TODO: Maybe add other image types > + if len(tarImgTypes) == 0: > + bb.debug(1, "Skipping check, no 'tar' image specified") > + return > + > + imgDeployDir = d.getVar("IMGDEPLOYDIR", True) > + imgName = d.getVar("IMAGE_NAME", True) > + imgNameSuffix = d.getVar("IMAGE_NAME_SUFFIX", True) > + > + for tarFileExt in tarImgTypes: > + archiveFilename = (imgName + imgNameSuffix + "." + tarFileExt) > + archiveFilePath = os.path.join(imgDeployDir, archiveFilename) > + > + oe.image.check_file_ownership_tar(d, archiveFilePath) > +} > + > +IMAGE_POSTPROCESS_COMMAND ?= " check_image_file_ownership " > > # some default locales > IMAGE_LINGUAS ?= "de-de fr-fr en-gb" > diff --git a/meta/lib/oe/image.py b/meta/lib/oe/image.py > new file mode 100644 > index 0000000000..f1ba6f2613 > --- /dev/null > +++ b/meta/lib/oe/image.py > @@ -0,0 +1,114 @@ > +# Helper function for image building > + > +def check_file_ownership_tar(d, archiveFilePath): > + """ > + Verifies file ownership meta-data in image tarball matches users > + and groups in shadow database (/etc/passwd and /etc/group files). > + """ > + import sys, os, tarfile > + bb.debug(1, "Running check_file_ownership() on image '{0!s}'".format( > archiveFilePath)) > + > + try: > + archiveName = os.path.basename(archiveFilePath) > + if not archiveName: > + archiveName = archiveFilePath > + > + # maps > + unameMap = {} # username -> passwd file entry array > + gnameMap = {} # groupname -> group file entry array > + uidMap = {} # uid -> passwd file entry array > + gidMap = {} # gid -> group file entry array > + fileMap = {} # filepath -> TarInfo object > + > + def read_shadow_file(fdName, fd, colCount, mapsList): > + """ Reads a colon (:) delimited shadow database file into > mapsList """ > + for mapObj,_ in mapsList: > + if len(mapObj) != 0: > + raise Exception("Already read '{0!s}' > file.".format(fdName)) > + > + for line in fd: > + line = line.decode("utf-8").strip() > + words = line.split(":") > + > + if len(words) != colCount: > + raise Exception("Malformed '{0!s}' file. Expected > {1!s} cols.".format(fdName, colCount)) > + > + for mapObj,mapKeyCol in mapsList: > + mapKey = words[mapKeyCol] > + if len(mapKey) < 1: > + raise Exception("Map key in '{0!s}' must be at > least one char long.".format(fdName)) > + > + if mapKey in mapObj: > + raise Exception("Malformed '{0!s}' file. Did not > expect to find '{0!s}' in map.".format(fdName, mapKey)) > + > + mapObj[mapKey] = words > + > + for mapObj,_ in mapsList: > + if len(mapObj) == 0: > + raise Exception("'{0!s}' is empty.".format(fdName)) > + > + bb.debug(1, "Read the archive and populate maps") > + with tarfile.open(name=archiveFilePath, mode='r:*') as tar: > + for info in tar: > + if info.name in fileMap: > + raise Exception("Duplicate entry for '{0!s}' > found.".format(info.name)) > + > + fileMap[info.name] = info > + > + # populate shadow db maps > + if info.name == './etc/passwd': > read_shadow_file(fdName=info.name, fd=tar.extractfile(info), colCount=7, > mapsList=[(unameMap, 0), (uidMap, 2), ]) > + elif info.name == './etc/group': > read_shadow_file(fdName=info.name, fd=tar.extractfile(info), colCount=4, > mapsList=[(gnameMap, 0), (gidMap, 2), ]) > + > + bb.debug(1, "Check for no shadow db") > + shadowItemCount = 0 > + for mapObj in [unameMap, gnameMap, uidMap, gidMap]: > + shadowItemCount = shadowItemCount + len(mapObj) > + if shadowItemCount == 0: > + bb.warn(1, "check_file_ownership(): Skip; no shadow database > in image '{0!s}'".format(archiveName)) > + return > + > + bb.debug(1, "Map sanity check") > + for mapObj in [unameMap, gnameMap, uidMap, gidMap, fileMap]: > + if len(mapObj) < 1: > + raise Exception("Uh oh. Empty map found.") > + > + def badFileError(errorMessage, info, shadowEntry=None): > + linkname = "" > + if info.linkname: > + linkname = " -> {0!s}".format(info.linkname) > + > + bb.error("BAD FILE '{0!s}': {1!s}".format(info.name, > errorMessage)) > + bb.error("## {info.uid!s}:{info.gid!s} > ({info.uname!s}:{info.gname!s}) 0{info.mode:o} > {info.name!s}{linkname!s}".format(info=info, > linkname=linkname)) > + if shadowEntry: > + bb.error("## {0!s}".format(shadowEntry)) > + > + bb.debug(1, "Check for bad files") > + for filepath,info in fileMap.items(): > + if not info.uname in unameMap: > + badFileError("uname not in unameMap", info) > + continue > + > + if not info.gname in gnameMap: > + badFileError("gname not in gnameMap", info) > + continue > + > + if not str(info.uid) in uidMap: > + badFileError("Uid '{0!s}' not found".format(info.uid), > info) > + > + if not str(info.gid) in gidMap: > + badFileError("Gid '{0!s}' not found".format(info.gid), > info) > + > + unameEntry = unameMap[info.uname] > + gnameEntry = gnameMap[info.gname] > + > + if str(info.uid) != unameEntry[2]: > + badFileError("uid mismatch, expecting '{0!s}' for uname > '{1!s}'".format(unameEntry[2], info.uname), info, unameEntry) > + > + if str(info.gid) != gnameEntry[2]: > + badFileError("gid mismatch, expecting '{0!s}' for gname > '{1!s}'".format(gnameEntry[2], info.gname), info, gnameEntry) > + > + bb.debug(1, "Successfully verified image > '{0!s}'".format(archiveName)) > + > + # Error on any exceptions > + except Exception as e: > + bb.error("check_file_ownership() exception: {0!s}".format(e)) > -- > 2.13.2 > > -- > _______________________________________________ > Openembedded-core mailing list > Openembedded-core@lists.openembedded.org > http://lists.openembedded.org/mailman/listinfo/openembedded-core > -- Christopher Larson kergoth at gmail dot com Founder - BitBake, OpenEmbedded, OpenZaurus Senior Software Engineer, Mentor Graphics
-- _______________________________________________ Openembedded-core mailing list Openembedded-core@lists.openembedded.org http://lists.openembedded.org/mailman/listinfo/openembedded-core