On Mon, Nov 11, 2019 at 02:38:15PM +0000, Daniel P. Berrangé wrote:
As part of an goal to eliminate Perl from libvirt build tools, rewrite the check-aclrules.pl tool in Python. This was a straight conversion, manually going line-by-line to change the syntax from Perl to Python. Thus the overall structure of the file and approach is the same. Signed-off-by: Daniel P. Berrangé <berrange@xxxxxxxxxx> --- Makefile.am | 1 + scripts/check-aclrules.py | 263 ++++++++++++++++++++++++++++++++++++++ src/Makefile.am | 4 +- src/check-aclrules.pl | 252 ------------------------------------ 4 files changed, 265 insertions(+), 255 deletions(-) create mode 100755 scripts/check-aclrules.py delete mode 100755 src/check-aclrules.pl diff --git a/Makefile.am b/Makefile.am index 407a664626..f28b07d814 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,7 @@ EXTRA_DIST = \ AUTHORS.in \ scripts/augeas-gentest.py \ scripts/check-aclperms.py \ + scripts/check-aclrules.py \ scripts/check-drivername.py \ scripts/check-driverimpls.py \ scripts/check-spacing.py \ diff --git a/scripts/check-aclrules.py b/scripts/check-aclrules.py new file mode 100755 index 0000000000..3fab126a4a --- /dev/null +++ b/scripts/check-aclrules.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013-2019 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see +# <http://www.gnu.org/licenses/>. +# +# This script validates that the driver implementation of any +# public APIs contain ACL checks. +# +# As the script reads each source file, it attempts to identify +# top level function names. +# +# When reading the body of the functions, it looks for anything +# that looks like an API called named XXXEnsureACL. It will +# validate that the XXX prefix matches the name of the function +# it occurs in. +# +# When it later finds the virDriverPtr table, for each entry +# point listed, it will validate if there was a previously +# detected EnsureACL call recorded. +# + +from __future__ import print_function + +import re +import sys + +whitelist = { + "connectClose": True, + "connectIsEncrypted": True, + "connectIsSecure": True, + "connectIsAlive": True, + "networkOpen": True, + "networkClose": True, + "nwfilterOpen": True, + "nwfilterClose": True, + "secretOpen": True, + "secretClose": True, + "storageOpen": True, + "storageClose": True, + "interfaceOpen": True, + "interfaceClose": True, + "connectURIProbe": True, + "localOnly": True, + "domainQemuAttach": True, +} + +# XXX this vzDomainMigrateConfirm3Params looks +# bogus - determine why it doesn't have a valid +# ACL check. +implwhitelist = { + "vzDomainMigrateConfirm3Params": True, +} + +lastfile = None + + +def fixup_name(name): + name.replace("Nwfilter", "NWFilter") + name.replace("Pm", "PM") + name.replace("Scsi", "SCSI") + if name.endswith("Xml"): + name = name[:-3] + "XML" + elif name.endswith("Uri"): + name = name[:-3] + "URI" + elif name.endswith("Uuid"): + name = name[:-4] + "UUID" + elif name.endswith("Id"): + name = name[:-2] + "ID" + elif name.endswith("Mac"): + name = name[:-3] + "MAC" + elif name.endswith("Cpu"): + name = name[:-3] + "MAC" + elif name.endswith("Os"): + name = name[:-2] + "OS" + elif name.endswith("Nmi"): + name = name[:-3] + "NMI" + elif name.endswith("Fstrim"): + name = name[:-6] + "FSTrim" + elif name.endswith("Wwn"): + name = name[:-3] + "WWN" + + return name + + +def name_to_ProcName(name): + elems = [] + if name.find("_") != -1 or name.lower() in ["open", "close"]: + elems = [n.lower().capitalize() for n in name.split("_")] + else: + elems = [name] + + elems = [fixup_name(n) for n in elems] + procname = "".join(elems) + + return procname[0:1].lower() + procname[1:] + + +proto = sys.argv[1] + +filteredmap = {} +with open(proto, "r") as fh: + incomment = False + filtered = False + + for line in fh: + if line.find("/**") != -1: + incomment = True + filtered = False + elif incomment: + if line.find("* @aclfilter") != -1: + filtered = True + elif filtered: + m = re.search(r'''REMOTE_PROC_(.*)\s+=\s*\d+''', line) + if m is not None: + api = name_to_ProcName(m.group(1)) + # Event filtering is handled in daemon/remote.c + # instead of drivers + if line.find("_EVENT_REGISTER") == -1: + filteredmap[api] = True + incomment = False + + +def process_file(filename): + brace = 0 + maybefunc = None + intable = False + table = None + + acls = {} + aclfilters = {} + errs = False + with open(filename, "r") as fh: + lineno = 0 + for line in fh: + lineno = lineno + 1 + if brace == 0: + # Looks for anything which appears to be a function + # body name. Doesn't matter if we pick up bogus stuff + # here, as long as we don't miss valid stuff + m = re.search(r'''\b(\w+)\(''', line) + if m is not None: + maybefunc = m.group(1) + elif brace > 0: + ensureacl = re.search(r'''(\w+)EnsureACL''', line) + checkacl = re.search(r'''(\w+)CheckACL''', line) + stub = re.search(r'''\b(\w+)\(''', line) + if ensureacl is not None: + # Record the fact that maybefunc contains an + # ACL call, and make sure it is the right call! + func = ensureacl.group(1) + if func.startswith("vir"): + func = func[3:] + + if maybefunc is None: + print("%s:%d Unexpected check '%s' outside function" % + (filename, lineno, func), file=sys.stderr) + errs = True + else: + if not maybefunc.lower().endswith(func.lower()): + print("%s:%d Mismatch check 'vir%sEnsureACL'" + + "for function '%s'" % + (filename, lineno, func, maybefunc), + file=sys.stderr) + errs = True + acls[maybefunc] = True + elif checkacl: + # Record the fact that maybefunc contains an + # ACL filter call, and make sure it is the right call! + func = checkacl.group(1) + if func.startswith("vir"): + func = func[3:] + + if maybefunc is None: + print("%s:%d Unexpected check '%s' outside function" % + (filename, lineno, func), file=sys.stderr) + errs = True + else: + if not maybefunc.lower().endswith(func.lower()): + print("%s:%d Mismatch check 'vir%sEnsureACL' " + + "for function '%s'" % + (filename, lineno, func, maybefunc), + file=sys.stderr) + errs = True + aclfilters[maybefunc] = True + elif stub: + # Handles case where we replaced an API with a new + # one which adds new parameters, and we're left with + # a simple stub calling the new API. + callfunc = stub.group(1) + if callfunc in acls: + acls[maybefunc] = True + + if callfunc in aclfilters: + aclfilters[maybefunc] = True + + # Pass the vir*DriverPtr tables and make sure that + # every func listed there, has an impl which calls + # an ACL function + if intable: + assign = re.search(r'''\.(\w+)\s*=\s*(\w+),?''', line) + if line.find("}") != -1: + intable = False + table = None + elif assign is not None: + api = assign.group(1) + impl = assign.group(2) + + if (impl != "NULL" and + api not in ["no", "name"] and + table != "virStateDriver"): + if (impl not in acls and + api not in whitelist and + impl not in implwhitelist): + print("%s:%d Missing ACL check in " + + "function '%s' for '%s'" % + (filename, lineno, impl, api), + file=sys.stderr) + errs = True + + if api in filteredmap and impl not in aclfilters: + print("%s:%d Missing ACL filter in " + + "function '%s' for '%s'" % + (filename, lineno, impl, api), + file=sys.stderr) + errs = True + else: + m = re.search(r'''^(?:static\s+)?(vir(?:\w+)?Driver)\s+''', + line) + if m is not None: + name = m.group(1) + if name not in ["virNWFilterCallbackDriver", + "virNWFilterTechDriver", + "virDomainConfNWFilterDriver"]: + intable = True + table = name + + if line.find("{") != -1: + brace = brace + 1 + if line.find("}") != -1: + brace = brace - 1 +
The perl version actually counted the braces - hopefully we're not quirky enough to have multiple of them on one line.
+ return errs + + +status = 0 +for filename in sys.argv[2:]: + if process_file(filename): + status = 1 + +sys.exit(status)
Reviewed-by: Ján Tomko <jtomko@xxxxxxxxxx> Jano
Attachment:
signature.asc
Description: PGP signature
-- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list