summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan McGee <dan@archlinux.org>2017-10-29 16:16:50 -0500
committerDan McGee <dan@archlinux.org>2017-10-29 16:16:50 -0500
commita2d04864e2b500cb8b3a6d20392bd611cd185778 (patch)
tree503ddc8ed51d7c0b56d680c9e37a9d3ac64235a5
parent96cdbb3628139c2ef14ec022988c74df09c1bb88 (diff)
parentf73bf160de19e43d1c22a3db0f32b234b872963f (diff)
downloadnamcap-a2d04864e2b500cb8b3a6d20392bd611cd185778.tar.gz
namcap-a2d04864e2b500cb8b3a6d20392bd611cd185778.zip
Merge branch 'master' of https://git.archlinux.org/namcapHEADmaster
-rw-r--r--Namcap/depends.py5
-rw-r--r--Namcap/package.py2
-rw-r--r--Namcap/rules/__init__.py1
-rw-r--r--Namcap/rules/anyelf.py6
-rw-r--r--Namcap/rules/elffiles.py159
-rw-r--r--Namcap/rules/externalhooks.py5
-rw-r--r--Namcap/rules/fhs.py22
-rw-r--r--Namcap/rules/filenames.py6
-rw-r--r--Namcap/rules/javafiles.py5
-rw-r--r--Namcap/rules/licensepkg.py34
-rw-r--r--Namcap/rules/makepkgfunctions.py37
-rw-r--r--Namcap/rules/missingvars.py4
-rw-r--r--Namcap/rules/pathdepends.py2
-rw-r--r--Namcap/rules/perllocal.py3
-rw-r--r--Namcap/rules/rpath.py55
-rw-r--r--Namcap/rules/scrollkeeper.py3
-rw-r--r--Namcap/rules/sfurl.py4
-rw-r--r--Namcap/rules/shebangdepends.py42
-rw-r--r--Namcap/rules/sodepends.py97
-rw-r--r--Namcap/rules/symlink.py10
-rw-r--r--Namcap/rules/unusedsodepends.py5
-rw-r--r--Namcap/tests/makepkg.py7
-rw-r--r--Namcap/tests/package/test_shebangdepends.py64
-rw-r--r--Namcap/tests/package/test_symlink.py3
-rw-r--r--Namcap/tests/test_depends.py3
-rw-r--r--Namcap/tests/test_pacman.py4
-rw-r--r--Namcap/util.py82
-rw-r--r--namcap-tags4
-rw-r--r--parsepkgbuild.sh5
29 files changed, 396 insertions, 283 deletions
diff --git a/Namcap/depends.py b/Namcap/depends.py
index 3c115bf..40d27bf 100644
--- a/Namcap/depends.py
+++ b/Namcap/depends.py
@@ -21,10 +21,7 @@
"""Checks dependencies semi-smartly."""
-import re, os, os.path
-import subprocess
-import tempfile
-from Namcap.util import is_elf, script_type
+import re
from Namcap.ruleclass import *
import Namcap.tags
from Namcap import package
diff --git a/Namcap/package.py b/Namcap/package.py
index ece81b5..2da5b1d 100644
--- a/Namcap/package.py
+++ b/Namcap/package.py
@@ -110,7 +110,7 @@ class PacmanPackage(collections.MutableMapping):
for line in db.split('\n'):
if line.startswith('%'):
attrname = line.strip('%').lower()
- elif line.strip() != '':
+ elif line.strip() != '' and attrname:
self.setdefault(attrname, []).append(line)
elif db is not None:
raise TypeError("argument 'pkginfo' must be a string")
diff --git a/Namcap/rules/__init__.py b/Namcap/rules/__init__.py
index 7d313f7..e8775a0 100644
--- a/Namcap/rules/__init__.py
+++ b/Namcap/rules/__init__.py
@@ -59,6 +59,7 @@ from . import (
carch,
extravars,
invalidstartdir,
+ makepkgfunctions,
missingvars,
pkginfo,
pkgnameindesc,
diff --git a/Namcap/rules/anyelf.py b/Namcap/rules/anyelf.py
index 90172a3..1e497e4 100644
--- a/Namcap/rules/anyelf.py
+++ b/Namcap/rules/anyelf.py
@@ -22,7 +22,7 @@ Check for ELF files to see if a package should be 'any' architecture
"""
import os, re
-from Namcap.util import is_elf, clean_filename
+from Namcap.util import is_elf, is_static, clean_filename
from Namcap.ruleclass import *
class package(TarballRule):
@@ -38,8 +38,8 @@ class package(TarballRule):
if not entry.isfile():
continue
f = tar.extractfile(entry)
- # Archive files are considered as ELF (FS#24854)
- if f.read(4) in (b"\x7fELF", b"!<ar"):
+ # Ar files (static libs) are also architecture specific (FS#24854)
+ if is_elf(f) or is_static(f):
found_elffiles.append(entry.name)
f.close()
diff --git a/Namcap/rules/elffiles.py b/Namcap/rules/elffiles.py
index f8f16ac..e2dd7f5 100644
--- a/Namcap/rules/elffiles.py
+++ b/Namcap/rules/elffiles.py
@@ -19,10 +19,10 @@
#
import os
-import tempfile
-import subprocess
from elftools.elf.elffile import ELFFile
+from elftools.elf.dynamic import DynamicSection
+from elftools.elf.sections import SymbolTableSection
from Namcap.util import is_elf, clean_filename
from Namcap.ruleclass import *
@@ -30,55 +30,47 @@ from Namcap.ruleclass import *
# Valid directories for ELF files
valid_dirs = ['bin/', 'sbin/', 'usr/bin/', 'usr/sbin/', 'lib/',
'usr/lib/', 'usr/lib32/']
+# Questionable directories for ELF files
+# (Suppresses some output spam.)
+questionable_dirs = ['opt/']
class ELFPaths(TarballRule):
name = "elfpaths"
description = "Check about ELF files outside some standard paths."
def analyze(self, pkginfo, tar):
invalid_elffiles = []
+ questionable_elffiles = []
for entry in tar:
# is it a regular file ?
if not entry.isfile():
continue
# is it outside standard binary dirs ?
- is_outside_std_dirs = True
- for d in valid_dirs:
- if entry.name.startswith(d):
- is_outside_std_dirs = False
- break
- if not is_outside_std_dirs:
+ in_std_dirs = any(entry.name.startswith(d) for d in valid_dirs)
+ in_que_dirs = any(entry.name.startswith(d) for d in questionable_dirs)
+
+ if in_std_dirs:
continue
# is it an ELF file ?
f = tar.extractfile(entry)
- if f.read(4) == b"\x7fELF":
- invalid_elffiles.append(entry.name)
+ if is_elf(f):
+ if in_que_dirs:
+ questionable_elffiles.append(entry.name)
+ else:
+ invalid_elffiles.append(entry.name)
+ que_elfdirs = [d for d in questionable_dirs if any(f.startswith(d) for f in questionable_elffiles)]
self.errors = [("elffile-not-in-allowed-dirs %s", i)
for i in invalid_elffiles]
+ self.errors.extend(("elffile-in-questionable-dirs %s", i)
+ for i in que_elfdirs)
+ self.infos = [("elffile-not-in-allowed-dirs %s", i)
+ for i in questionable_elffiles]
-def _test_elf_and_extract(tar, entry):
- "Tests whether a Tar entry is an ELF file and returns the name of a temp file."
- if not entry.isfile():
- return
- f = tar.extractfile(entry)
- magic = f.read(4)
- if magic != b"\x7fELF":
- return
-
- # read the rest of file
- tmp = tempfile.NamedTemporaryFile(delete=False)
- tmp.write(magic + f.read())
- tmp.close()
- return tmp.name
class ELFTextRelocationRule(TarballRule):
"""
Check for text relocations in ELF files.
-
- Introduced by FS#26434. Text relocations are detected by the
- eu-findtextrel utility from elfutils. eu-findtextrel returns 0
- whenever the input file has a text relocation section.
"""
name = "elftextrel"
@@ -88,18 +80,18 @@ class ELFTextRelocationRule(TarballRule):
files_with_textrel = []
for entry in tar:
- tmpname = _test_elf_and_extract(tar, entry)
- if not tmpname:
+ if not entry.isfile():
continue
-
- try:
- ret = subprocess.call(["eu-findtextrel", tmpname],
- stdout=open(os.devnull, 'w'),
- stderr=open(os.devnull, 'w'))
- if ret == 0:
- files_with_textrel.append(entry.name)
- finally:
- os.unlink(tmpname)
+ fp = tar.extractfile(entry)
+ if not is_elf(fp):
+ continue
+ elffile = ELFFile(fp)
+ for section in elffile.iter_sections():
+ if not isinstance(section, DynamicSection):
+ continue
+ for tag in section.iter_tags():
+ if tag.entry.d_tag == 'DT_TEXTREL':
+ files_with_textrel.append(entry.name)
if files_with_textrel:
self.warnings = [("elffile-with-textrel %s", i)
@@ -121,26 +113,85 @@ class ELFExecStackRule(TarballRule):
exec_stacks = []
for entry in tar:
- tmpname = _test_elf_and_extract(tar, entry)
- if not tmpname:
+ if not entry.isfile():
continue
+ fp = tar.extractfile(entry)
+ if not is_elf(fp):
+ continue
+ elffile = ELFFile(fp)
+ for segment in elffile.iter_segments():
+ if segment['p_type'] != 'PT_GNU_STACK':
+ continue
- try:
- fp = open(tmpname, 'rb')
- elffile = ELFFile(fp)
-
- for segment in elffile.iter_segments():
- if segment['p_type'] != 'PT_GNU_STACK': continue
-
- mode = segment['p_flags']
- if mode & 1: exec_stacks.append(entry.name)
-
- fp.close()
- finally:
- os.unlink(tmpname)
+ mode = segment['p_flags']
+ if mode & 1:
+ exec_stacks.append(entry.name)
if exec_stacks:
self.warnings = [("elffile-with-execstack %s", i)
for i in exec_stacks]
+class ELFGnuRelroRule(TarballRule):
+ """
+ Check for read-only relocation in ELF files.
+
+ Introduced by FS#26435. Uses pyelftools to check for GNU_RELRO.
+ """
+ # not smart enough for full/partial RELRO (DT_BIND_NOW?)
+
+ name = "elfgnurelro"
+ description = "Check for RELRO in ELF files."
+
+ def analyze(self, pkginfo, tar):
+ missing_relro = []
+
+ for entry in tar:
+ if not entry.isfile():
+ continue
+ fp = tar.extractfile(entry)
+ if not is_elf(fp):
+ continue
+ elffile = ELFFile(fp)
+ if any(seg['p_type'] == 'PT_GNU_RELRO' for seg in elffile.iter_segments()):
+ continue
+ missing_relro.append(entry.name)
+
+ if missing_relro:
+ self.warnings = [("elffile-without-relro %s", i)
+ for i in missing_relro]
+
+class ELFUnstrippedRule(TarballRule):
+ """
+ Checks for unstripped ELF files. Uses pyelftools to check if
+ .symtab exists.
+
+ Introduced by FS#27485.
+ """
+
+ name = "elfunstripped"
+ description = "Check for unstripped ELF files."
+
+ def analyze(self, pkginfo, tar):
+ unstripped_binaries = []
+
+ for entry in tar:
+ if not entry.isfile():
+ continue
+ fp = tar.extractfile(entry)
+ if not is_elf(fp):
+ continue
+ elffile = ELFFile(fp)
+ for section in elffile.iter_sections():
+ if not isinstance(section, SymbolTableSection):
+ continue
+
+ if section['sh_entsize'] == 0:
+ continue
+
+ if section.name == '.symtab':
+ unstripped_binaries.append(entry.name)
+ if unstripped_binaries:
+ self.warnings = [("elffile-unstripped %s", i)
+ for i in unstripped_binaries]
+
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/externalhooks.py b/Namcap/rules/externalhooks.py
index aad2bd2..aa6f465 100644
--- a/Namcap/rules/externalhooks.py
+++ b/Namcap/rules/externalhooks.py
@@ -31,6 +31,11 @@ class ExternalHooksRule(TarballRule):
'xdg-icon-resource',
'gconfpkg',
'gio-querymodules',
+ 'fc-cache',
+ 'mkfontscale',
+ 'mkfontdir',
+ 'systemd-sysusers',
+ 'systemd-tmpfiles',
]
def analyze(self, pkginfo, tar):
if ".INSTALL" not in tar.getnames():
diff --git a/Namcap/rules/fhs.py b/Namcap/rules/fhs.py
index be583ef..6a40ca4 100644
--- a/Namcap/rules/fhs.py
+++ b/Namcap/rules/fhs.py
@@ -29,7 +29,7 @@ class FHSRule(TarballRule):
'etc/', 'opt/',
'lib/modules',
'usr/bin/', 'usr/include/', 'usr/lib/', 'usr/lib32/',
- 'usr/sbin/', 'usr/share/',
+ 'usr/sbin/', 'usr/share/', 'usr/src/',
'var/cache/', 'var/lib/', 'var/log/', 'var/opt/',
'var/spool/', 'var/state/',
'.PKGINFO', '.INSTALL', '.CHANGELOG', '.MTREE', '.BUILDINFO',
@@ -79,13 +79,14 @@ class FHSManpagesRule(TarballRule):
for i in tar.getmembers():
if not i.isfile():
continue
+ if i.name.startswith(gooddir):
+ continue
if i.name.startswith(bad_dir):
self.errors.append(("non-fhs-man-page %s", i.name))
- elif not i.name.startswith(gooddir):
- #Check everything else to see if it has a 'man' path component
- for part in i.name.split(os.sep):
- if part == "man":
- self.warnings.append(("potential-non-fhs-man-page %s", i.name))
+ continue
+ #Check everything else to see if it has a 'man' path component
+ if "man" in i.name.split(os.sep):
+ self.warnings.append(("potential-non-fhs-man-page %s", i.name))
class FHSInfoPagesRule(TarballRule):
name = "fhs-infopages"
@@ -94,12 +95,13 @@ class FHSInfoPagesRule(TarballRule):
for i in tar.getmembers():
if not i.isfile():
continue
+ if i.name.startswith('usr/share/info'):
+ continue
if i.name.startswith('usr/info'):
self.errors.append(("non-fhs-info-page %s", i.name))
- elif not i.name.startswith('usr/share/info'):
- for part in i.name.split(os.sep):
- if part == "info":
- self.warnings.append(("potential-non-fhs-info-page %s", i.name))
+ continue
+ if "info" in i.name.split(os.sep):
+ self.warnings.append(("potential-non-fhs-info-page %s", i.name))
class RubyPathsRule(TarballRule):
name = "rubypaths"
diff --git a/Namcap/rules/filenames.py b/Namcap/rules/filenames.py
index d7cba48..c5f5f0e 100644
--- a/Namcap/rules/filenames.py
+++ b/Namcap/rules/filenames.py
@@ -31,9 +31,7 @@ class package(TarballRule):
description = "Checks for invalid filenames."
def analyze(self, pkginfo, tar):
for i in tar.getnames():
- for c in i:
- if c not in VALID_CHARS:
- self.warnings.append(("invalid-filename", i))
- break
+ if not all(c in VALID_CHARS for c in i):
+ self.warnings.append(("invalid-filename", i))
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/javafiles.py b/Namcap/rules/javafiles.py
index 080174f..40ff856 100644
--- a/Namcap/rules/javafiles.py
+++ b/Namcap/rules/javafiles.py
@@ -20,6 +20,7 @@
import os
from Namcap.ruleclass import *
+from Namcap.util import is_java
class JavaFiles(TarballRule):
name = "javafiles"
@@ -38,11 +39,11 @@ class JavaFiles(TarballRule):
continue
# is it a CLASS file ?
f = tar.extractfile(entry)
- if f.read(4) == b"\xCA\xFE\xBA\xBE":
+ if is_java(f):
javas.append(entry.name)
#self.infos.append( ('java-class-file-found %s', entry.name) )
f.close()
- if len(javas) > 0:
+ if javas:
reasons = pkginfo.detected_deps.setdefault('java-runtime', [])
reasons.append( ('java-runtime-needed %s', ', '.join(javas)) )
diff --git a/Namcap/rules/licensepkg.py b/Namcap/rules/licensepkg.py
index e4b5e9b..026caf4 100644
--- a/Namcap/rules/licensepkg.py
+++ b/Namcap/rules/licensepkg.py
@@ -26,22 +26,22 @@ class package(TarballRule):
def analyze(self, pkginfo, tar):
if 'license' not in pkginfo or len(pkginfo["license"]) == 0:
self.errors.append(("missing-license", ()))
- else:
- licensepaths = [x for x in tar.getnames() if x.startswith('usr/share/licenses') and not x.endswith('/')]
- licensedirs = [os.path.split(os.path.split(x)[0])[1] for x in licensepaths]
- licensefiles = [os.path.split(x)[1] for x in licensepaths]
- # Check all licenses for validity
- for license in pkginfo["license"]:
- lowerlicense, _, sublicense = license.lower().partition(':')
- if lowerlicense.startswith('custom') or lowerlicense in ("bsd", "mit", "isc", "python", "zlib", "libpng"):
- if pkginfo["name"] not in licensedirs:
- self.errors.append(("missing-custom-license-dir usr/share/licenses/%s", pkginfo["name"]))
- elif len(licensefiles) == 0:
- self.errors.append(("missing-custom-license-file usr/share/licenses/%s/*", pkginfo["name"]))
- # A common license
- else:
- commonlicenses = [x.lower() for x in os.listdir('/usr/share/licenses/common')]
- if lowerlicense not in commonlicenses:
- self.errors.append(("not-a-common-license %s", license))
+ return
+ licensepaths = [x for x in tar.getnames() if x.startswith('usr/share/licenses') and not x.endswith('/')]
+ licensedirs = [os.path.split(os.path.split(x)[0])[1] for x in licensepaths]
+ licensefiles = [os.path.split(x)[1] for x in licensepaths]
+ # Check all licenses for validity
+ for license in pkginfo["license"]:
+ lowerlicense, _, sublicense = license.lower().partition(':')
+ if lowerlicense.startswith('custom') or lowerlicense in ("bsd", "mit", "isc", "python", "zlib", "libpng"):
+ if pkginfo["name"] not in licensedirs:
+ self.errors.append(("missing-custom-license-dir usr/share/licenses/%s", pkginfo["name"]))
+ elif len(licensefiles) == 0:
+ self.errors.append(("missing-custom-license-file usr/share/licenses/%s/*", pkginfo["name"]))
+ # A common license
+ else:
+ commonlicenses = [x.lower() for x in os.listdir('/usr/share/licenses/common')]
+ if lowerlicense not in commonlicenses:
+ self.errors.append(("not-a-common-license %s", license))
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/makepkgfunctions.py b/Namcap/rules/makepkgfunctions.py
new file mode 100644
index 0000000..fa20f5f
--- /dev/null
+++ b/Namcap/rules/makepkgfunctions.py
@@ -0,0 +1,37 @@
+#
+# namcap rules - makepkgfunctions
+# Copyright (C) 2017 Kyle Keen <keenerd@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import re
+from Namcap.ruleclass import *
+
+class package(PkgbuildRule):
+ name = "makepkgfunctions"
+ description = "Looks for calls to makepkg functionality"
+ def analyze(self, pkginfo, tar):
+ bad_calls = ['msg', 'msg2', 'warning', 'error', 'plain']
+ regex = re.compile('^\s+(%s) ' % '|'.join(bad_calls))
+ hits = set()
+ for i in pkginfo.pkgbuild:
+ if regex.match(i):
+ call = regex.match(i).group(1)
+ hits.add(call)
+ for i in hits:
+ self.warnings.append(("makepkg-function-used %s", i))
+
+# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/missingvars.py b/Namcap/rules/missingvars.py
index 440f883..2b8811c 100644
--- a/Namcap/rules/missingvars.py
+++ b/Namcap/rules/missingvars.py
@@ -65,9 +65,9 @@ class TagsRule(PkgbuildRule):
maintainertag = 0
idtag = 0
for i in pkginfo.pkgbuild:
- if re.match("#\s*Contributor\s*:", i) != None:
+ if re.match("#\s*Contributor\s*:", i):
contributortag = 1
- if re.match("#\s*Maintainer\s*:", i) != None:
+ if re.match("#\s*Maintainer\s*:", i):
maintainertag = 1
if contributortag != 1:
diff --git a/Namcap/rules/pathdepends.py b/Namcap/rules/pathdepends.py
index e50a6f6..5bc4313 100644
--- a/Namcap/rules/pathdepends.py
+++ b/Namcap/rules/pathdepends.py
@@ -24,7 +24,7 @@ If a certain path is detected then a certain dependency is expected.
Anything fancier than this should get its own rule.
"""
-import os, re
+import re
from Namcap.ruleclass import *
class PathDependsRule(TarballRule):
diff --git a/Namcap/rules/perllocal.py b/Namcap/rules/perllocal.py
index ac622b4..24923d9 100644
--- a/Namcap/rules/perllocal.py
+++ b/Namcap/rules/perllocal.py
@@ -23,9 +23,8 @@ class package(TarballRule):
name = "perllocal"
description = "Verifies the absence of perllocal.pod."
def analyze(self, pkginfo, tar):
- j = 'perllocal.pod'
for i in tar.getnames():
- if i[-len(j):] == j:
+ if i.endswith('perllocal.pod'):
self.errors.append(("perllocal-pod-present %s", i))
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/rpath.py b/Namcap/rules/rpath.py
index a2d1193..4da040c 100644
--- a/Namcap/rules/rpath.py
+++ b/Namcap/rules/rpath.py
@@ -17,37 +17,28 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-import os, subprocess, re
-import tempfile
-from Namcap.util import is_elf, clean_filename
+from Namcap.util import is_elf
from Namcap.ruleclass import *
+from elftools.elf.elffile import ELFFile
+from elftools.elf.dynamic import DynamicSection
+
allowed = ['/usr/lib', '/usr/lib32', '/lib', '$ORIGIN', '${ORIGIN}']
allowed_toplevels = [s + '/' for s in allowed]
warn = ['/usr/local/lib']
-libpath = re.compile('Library rpath: \[(.*)\]')
-
-def get_rpaths(filename):
- p = subprocess.Popen(["readelf", "-d", filename],
- env={'LANG': 'C'},
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- var = p.communicate()
- if p.returncode != 0:
- raise IOError("unable to read ELF file")
- for j in var[0].decode('ascii').splitlines():
- n = libpath.search(j)
- # Is this a Library rpath: line?
- if n is None:
+def get_rpaths(fileobj):
+ elffile = ELFFile(fileobj)
+ for section in elffile.iter_sections():
+ if not isinstance(section, DynamicSection):
continue
-
- if ":" in n.group(1):
- rpaths = n.group(1).split(':')
- else:
- rpaths = [n.group(1)]
- for path in rpaths:
- yield path
+ for tag in section.iter_tags():
+ if tag.entry.d_tag != 'DT_RPATH':
+ continue
+ rpaths = tag.rpath
+ rpaths = rpaths.split(':')
+ for path in rpaths:
+ yield path
class package(TarballRule):
name = "rpath"
@@ -58,18 +49,11 @@ class package(TarballRule):
continue
# is it an ELF file ?
- f = tar.extractfile(entry)
- elf = f.read()
- f.close()
- if elf[:4] != b"\x7fELF":
- continue # not an ELF file
-
- # write it to a temporary file
- f = tempfile.NamedTemporaryFile(delete = False)
- f.write(elf)
- f.close()
+ fileobj = tar.extractfile(entry)
+ if not is_elf(fileobj):
+ continue
- for path in get_rpaths(f.name):
+ for path in get_rpaths(fileobj):
path_ok = path in allowed
for allowed_toplevel in allowed_toplevels:
if path.startswith(allowed_toplevel):
@@ -83,6 +67,5 @@ class package(TarballRule):
self.warnings.append(("insecure-rpath %s %s",
(path, entry.name)))
- os.unlink(f.name)
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/scrollkeeper.py b/Namcap/rules/scrollkeeper.py
index 1c09e4e..8c813b8 100644
--- a/Namcap/rules/scrollkeeper.py
+++ b/Namcap/rules/scrollkeeper.py
@@ -26,8 +26,7 @@ class package(TarballRule):
def analyze(self, pkginfo, tar):
scroll = re.compile("var.*/scrollkeeper/?$")
for i in tar.getnames():
- n = scroll.search(i)
- if n != None:
+ if scroll.search(i):
self.errors.append(("scrollkeeper-dir-exists %s", i))
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/sfurl.py b/Namcap/rules/sfurl.py
index 7f4dcc6..f3b6874 100644
--- a/Namcap/rules/sfurl.py
+++ b/Namcap/rules/sfurl.py
@@ -26,9 +26,9 @@ class package(PkgbuildRule):
def analyze(self, pkginfo, tar):
if 'source' in pkginfo:
for source in pkginfo["source"]:
- if re.match('(http://|ftp://)\w+.dl.(sourceforge|sf).net', source) != None:
+ if re.match('(http://|ftp://)\w+.dl.(sourceforge|sf).net', source):
self.warnings.append(("specific-sourceforge-mirror", ()))
- if re.match('(http://|ftp://)dl.(sourceforge|sf).net', source) != None:
+ if re.match('(http://|ftp://)dl.(sourceforge|sf).net', source):
self.warnings.append(("using-dl-sourceforge", ()))
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/rules/shebangdepends.py b/Namcap/rules/shebangdepends.py
index 567acf5..7d83ff4 100644
--- a/Namcap/rules/shebangdepends.py
+++ b/Namcap/rules/shebangdepends.py
@@ -21,13 +21,10 @@
"""Checks dependencies on programs specified in shebangs."""
-import re
import os
-import tempfile
-import subprocess
-import pyalpm
+import shutil
import Namcap.package
-from Namcap.util import script_type
+from Namcap.util import is_script, script_type
from Namcap.ruleclass import *
def scanshebangs(fileobj, filename, scripts):
@@ -39,21 +36,13 @@ def scanshebangs(fileobj, filename, scripts):
"""
# test magic bytes
- magic = fileobj.read(2)
- if magic != b"#!":
+ if not is_script(fileobj):
return
- # read the rest of file
- tmp = tempfile.NamedTemporaryFile(delete=False)
- tmp.write(magic + fileobj.read())
- tmp.close()
-
- try:
- cmd = script_type(tmp.name)
- if cmd != None:
- assert(isinstance(cmd, str))
- scripts.setdefault(cmd, set()).add(filename)
- finally:
- os.unlink(tmp.name)
+ # process shebang line
+ cmd = script_type(fileobj)
+ if cmd != None:
+ assert(isinstance(cmd, str))
+ scripts.setdefault(cmd, set()).add(filename)
def findowners(scriptlist):
"""
@@ -68,14 +57,12 @@ def findowners(scriptlist):
scriptfound = set()
for s in scriptlist:
- p = subprocess.Popen(["which", s],
- stdout = subprocess.PIPE, stderr = subprocess.PIPE)
- out, _ = p.communicate()
- if p.returncode != 0:
+ out = shutil.which(s)
+ if not out:
continue
# strip leading slash
- scriptpath = out.strip()[1:].decode('utf-8', 'surrogateescape')
+ scriptpath = out.lstrip('/')
for pkg in Namcap.package.get_installed_packages():
pkg_files = [fname for fname, fsize, fmode in pkg.files]
if scriptpath in pkg_files:
@@ -85,13 +72,6 @@ def findowners(scriptlist):
orphans = list(set(scriptlist) - scriptfound)
return pkglist, orphans
-def getprovides(depends, provides):
- for i in depends.keys():
- pac = load(i)
-
- if pac != None and 'provides' in pac and pac["provides"] != None:
- provides[i] = pac["provides"]
-
class ShebangDependsRule(TarballRule):
name = "shebangdepends"
description = "Checks dependencies semi-smartly."
diff --git a/Namcap/rules/sodepends.py b/Namcap/rules/sodepends.py
index 92826af..bce3a40 100644
--- a/Namcap/rules/sodepends.py
+++ b/Namcap/rules/sodepends.py
@@ -25,66 +25,52 @@ from collections import defaultdict
import re
import os
import subprocess
-import tempfile
import Namcap.package
from Namcap.ruleclass import *
+from Namcap.util import is_elf
+from Namcap.rules.rpath import get_rpaths
-libcache = {'i686': {}, 'x86-64': {}}
-
-def figurebitsize(line):
- """
- Given a line of output from readelf (usually Shared library:) return
- 'i686' or 'x86-64' if the binary is a 32bit or 64bit binary
- """
+from elftools.elf.enums import ENUM_D_TAG
+from elftools.elf.elffile import ELFFile
+from elftools.elf.dynamic import DynamicSection
- address = line.split()[0]
- if len(address) == 18: # + '0x' + 16 digits
- return 'x86-64'
- else:
- return 'i686'
+libcache = {'i686': {}, 'x86-64': {}}
-def scanlibs(fileobj, filename, sharedlibs):
+def scanlibs(fileobj, filename, custom_libs):
"""
- Run "readelf -d" on a file-like object (e.g. a TarFile)
+ Find shared libraries in a file-like binary object
If it depends on a library, store that library's path.
- sharedlibs: a dictionary { library => set(ELF files using that library) }
+ returns: a dictionary { library => set(ELF files using that library) }
"""
- shared = re.compile('Shared library: \[(.*)\]')
-
- # test magic bytes
- magic = fileobj.read(4)
- if magic[:4] != b"\x7fELF":
- return
-
- # read the rest of file
- tmp = tempfile.NamedTemporaryFile(delete=False)
- tmp.write(magic + fileobj.read())
- tmp.close()
-
- try:
- p = subprocess.Popen(["readelf", "-d", tmp.name],
- env = {"LANG": "C"},
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- var = p.communicate()
- assert(p.returncode == 0)
- for j in var[0].decode('ascii').splitlines():
- n = shared.search(j)
- # Is this a Shared library: line?
- if n != None:
- # Find out its architecture
- architecture = figurebitsize(j)
- try:
- libpath = os.path.abspath(
- libcache[architecture][n.group(1)])[1:]
- sharedlibs.setdefault(libpath, set()).add(filename)
- except KeyError:
- # We didn't know about the library, so add it for fail later
- sharedlibs.setdefault(n.group(1), set()).add(filename)
- finally:
- os.unlink(tmp.name)
+
+ if not is_elf(fileobj):
+ return {}
+
+ elffile = ELFFile(fileobj)
+ sharedlibs = defaultdict(set)
+ for section in elffile.iter_sections():
+ if not isinstance(section, DynamicSection):
+ continue
+ for tag in section.iter_tags():
+ # DT_NEEDED means shared library
+ if tag.entry.d_tag != 'DT_NEEDED':
+ continue
+ bitsize = elffile.elfclass
+ architecture = {32:'i686', 64:'x86-64'}[bitsize]
+ libname = tag.needed
+ if libname in custom_libs:
+ sharedlibs[custom_libs[libname][1:]].add(filename)
+ continue
+ try:
+ libpath = os.path.abspath(
+ libcache[architecture][libname])[1:]
+ sharedlibs[libpath].add(filename)
+ except KeyError:
+ # We didn't know about the library, so add it for fail later
+ sharedlibs[libname].add(filename)
+ return sharedlibs
def finddepends(liblist):
"""
@@ -152,12 +138,21 @@ class SharedLibsRule(TarballRule):
dependlist = {}
filllibcache()
os.environ['LC_ALL'] = 'C'
+ pkg_so_files = ['/' + n for n in tar.getnames() if '.so' in n]
for entry in tar:
if not entry.isfile():
continue
f = tar.extractfile(entry)
- scanlibs(f, entry.name, liblist)
+ # find anything that could be rpath related
+ rpath_files = {}
+ if is_elf(f):
+ rpaths = list(get_rpaths(f))
+ f.seek(0)
+ for n in pkg_so_files:
+ if any(n.startswith(rp) for rp in rpaths):
+ rpath_files[os.path.basename(n)] = n
+ liblist.update(scanlibs(f, entry.name, rpath_files))
f.close()
# Ldd all the files and find all the link and script dependencies
diff --git a/Namcap/rules/symlink.py b/Namcap/rules/symlink.py
index c43e498..f595bc5 100644
--- a/Namcap/rules/symlink.py
+++ b/Namcap/rules/symlink.py
@@ -18,12 +18,20 @@
#
import os
from Namcap.ruleclass import *
+from Namcap.package import load_from_db
class package(TarballRule):
name = "symlink"
description = "Checks that symlinks point to the right place"
def analyze(self, pkginfo, tar):
- filenames = [s.name for s in tar]
+ filenames = set(s.name for s in tar)
+ depfilenames = set()
+ for d in pkginfo['depends']:
+ p = load_from_db(d)
+ if not p:
+ continue
+ depfilenames |= set(name for name,_,_ in p['files'])
+ filenames |= depfilenames
for i in tar:
if i.issym():
self.infos.append(("symlink-found %s points to %s", (i.name, i.linkname)))
diff --git a/Namcap/rules/unusedsodepends.py b/Namcap/rules/unusedsodepends.py
index 82bfcab..abfb3ba 100644
--- a/Namcap/rules/unusedsodepends.py
+++ b/Namcap/rules/unusedsodepends.py
@@ -55,10 +55,11 @@ class package(TarballRule):
# is it an ELF file ?
f = tar.extractfile(entry)
+ if not is_elf(f):
+ f.close()
+ continue
elf = f.read()
f.close()
- if elf[:4] != b"\x7fELF":
- continue # not an ELF file
# write it to a temporary file
f = tempfile.NamedTemporaryFile(delete = False)
diff --git a/Namcap/tests/makepkg.py b/Namcap/tests/makepkg.py
index a32a477..b3b0725 100644
--- a/Namcap/tests/makepkg.py
+++ b/Namcap/tests/makepkg.py
@@ -80,13 +80,10 @@ class MakepkgTest(unittest.TestCase):
os.chdir(pwd)
def run_rule_on_tarball(self, filename, rule):
- ret = subprocess.call(["unxz", '-f', filename + ".xz"])
- self.assertEqual(ret, 0)
-
# process PKGINFO
- pkg = Namcap.package.load_from_tarball(filename)
+ pkg = Namcap.package.load_from_tarball(filename + ".xz")
- tar = tarfile.open(filename)
+ tar = tarfile.open(filename + ".xz")
r = rule()
r.analyze(pkg, tar)
tar.close()
diff --git a/Namcap/tests/package/test_shebangdepends.py b/Namcap/tests/package/test_shebangdepends.py
new file mode 100644
index 0000000..dbacd86
--- /dev/null
+++ b/Namcap/tests/package/test_shebangdepends.py
@@ -0,0 +1,64 @@
+# namcap tests - shebangdepends
+# Copyright (C) 2016 Kyle Keen <keenerd@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+
+import os
+from Namcap.tests.makepkg import MakepkgTest
+import Namcap.rules.shebangdepends
+
+class ShebangDependsTest(MakepkgTest):
+ pkgbuild = """
+pkgname=__namcap_test_shebangdepends
+pkgver=1.0
+pkgrel=1
+pkgdesc="A package"
+arch=('any')
+url="http://www.example.com/"
+license=('GPL')
+depends=()
+source=()
+options=(!purge !zipman)
+build() {
+ cd "${srcdir}"
+ echo -e "#! /usr/bin/env python\nprint('a script')" > python_sample
+ echo -e "#!/bin\\xffary/da\\x00ta\ncrash?" > binary_sample
+}
+package() {
+ install -Dm755 "$srcdir/python_sample" "$pkgdir/usr/bin/python_sample"
+ install -Dm755 "$srcdir/binary_sample" "$pkgdir/usr/share/binary_sample"
+}
+"""
+ def test_shebangdepends(self):
+ "Package with missing python dependency"
+ pkgfile = "__namcap_test_shebangdepends-1.0-1-any.pkg.tar"
+ with open(os.path.join(self.tmpdir, "PKGBUILD"), "w") as f:
+ f.write(self.pkgbuild)
+ self.run_makepkg()
+ pkg, r = self.run_rule_on_tarball(
+ os.path.join(self.tmpdir, pkgfile),
+ Namcap.rules.shebangdepends.ShebangDependsRule
+ )
+ e, w, i = Namcap.depends.analyze_depends(pkg)
+ self.assertEqual(e, [
+ ('dependency-detected-not-included %s (%s)',
+ ('python', "programs ['python'] needed in scripts ['usr/bin/python_sample']"))
+ ])
+ self.assertEqual(w, [])
+
+# vim: set ts=4 sw=4 noet:
+
diff --git a/Namcap/tests/package/test_symlink.py b/Namcap/tests/package/test_symlink.py
index 43d20c5..92c8d51 100644
--- a/Namcap/tests/package/test_symlink.py
+++ b/Namcap/tests/package/test_symlink.py
@@ -46,6 +46,7 @@ package() {
ln -s ../nofile "${pkgdir}/usr/share/somelink2"
ln -s //usr/share/somedata "${pkgdir}/usr/share/validlink"
ln -s ../share/somedata "${pkgdir}/usr/share/validlink2"
+ ln -s /usr/include/math.h "${pkgdir}/usr/share/deplink"
}
"""
def test_symlink_files(self):
@@ -74,6 +75,8 @@ package() {
("usr/share/validlink", "//usr/share/somedata")),
("symlink-found %s points to %s",
("usr/share/validlink2", "../share/somedata")),
+ ("symlink-found %s points to %s",
+ ("usr/share/deplink", "/usr/include/math.h")),
]))
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/tests/test_depends.py b/Namcap/tests/test_depends.py
index 7c6a9dc..8e74192 100644
--- a/Namcap/tests/test_depends.py
+++ b/Namcap/tests/test_depends.py
@@ -19,10 +19,7 @@
# USA
#
-import os
import unittest
-import tempfile
-import shutil
import Namcap.depends
import Namcap.package
diff --git a/Namcap/tests/test_pacman.py b/Namcap/tests/test_pacman.py
index d406a27..0fe74ba 100644
--- a/Namcap/tests/test_pacman.py
+++ b/Namcap/tests/test_pacman.py
@@ -18,7 +18,7 @@ url="http://www.example.com/"
license=('GPL')
depends=('glibc' 'foobar')
optdepends=('libabc: provides the abc feature')
-provides=('yourpackage>=0.9')
+provides=('yourpackage=0.9')
options=('!libtool')
source=(ftp://ftp.example.com/pub/mypackage-0.1.tar.gz)
md5sums=('abcdefabcdef12345678901234567890')
@@ -68,6 +68,6 @@ class PkgbuildLoaderTests(unittest.TestCase):
def test_provides(self):
self.assertEqual(self.pkginfo['provides'], ["yourpackage"])
self.assertEqual(self.pkginfo['orig_provides'],
- ["yourpackage>=0.9"])
+ ["yourpackage=0.9"])
# vim: set ts=4 sw=4 noet:
diff --git a/Namcap/util.py b/Namcap/util.py
index 21d7163..f8d38dd 100644
--- a/Namcap/util.py
+++ b/Namcap/util.py
@@ -19,61 +19,47 @@
import os
import re
-import stat
-def _read_carefully(path, readcall):
- if not os.path.isfile(path):
- return False
- reset_perms = False
- if not os.access(path, os.R_OK):
- # don't mess with links we can't read
- if os.path.islink(path):
- return None
- reset_perms = True
- # attempt to make it readable if possible
- statinfo = os.stat(path)
- newmode = statinfo.st_mode | stat.S_IRUSR
- try:
- os.chmod(path, newmode)
- except IOError:
- return None
- fd = open(path, 'rb')
- val = readcall(fd)
- fd.close()
- # reset permissions if necessary
- if reset_perms:
- # set file back to original permissions
- os.chmod(path, statinfo.st_mode)
- return val
+def _file_has_magic(fileobj, magic_bytes):
+ length = len(magic_bytes)
+ magic = fileobj.read(length)
+ fileobj.seek(0)
+ return magic == magic_bytes
-def is_elf(path):
- """
- Given a file path, ensure it exists and peek at the first few bytes
- to determine if it is an ELF file.
- """
- magic = _read_carefully(path, lambda fd: fd.read(4))
- if not magic:
- return False
- # magic elf header, present in binaries and libraries
- if magic == b"\x7FELF":
- return True
- else:
- return False
+def is_elf(fileobj):
+ "Take file object, peek at the magic bytes to check if ELF file."
+ return _file_has_magic(fileobj, b"\x7fELF")
-def script_type(path):
- firstline = _read_carefully(path, lambda fd: fd.readline())
- firstline = firstline.decode('ascii', 'ignore')
+def is_static(fileobj):
+ "Take file object, peek at the magic bytes to check if static lib."
+ return _file_has_magic(fileobj, b"!<arch>\n")
+
+def is_script(fileobj):
+ "Take file object, peek at the magic bytes to check if script."
+ return _file_has_magic(fileobj, b"#!")
+
+def is_java(fileobj):
+ "Take file object, peek at the magic bytes to check if class file."
+ return _file_has_magic(fileobj, b"\xCA\xFE\xBA\xBE")
+
+def script_type(fileobj):
+ firstline = fileobj.readline()
+ fileobj.seek(0)
+ try:
+ firstline = firstline.decode('utf-8', 'strict')
+ except UnicodeDecodeError:
+ return None
if not firstline:
return None
script = re.compile('#!.*/(.*)')
m = script.match(firstline)
- if m != None:
- cmd = m.group(1).split()
- name = cmd[0]
- if name == 'env':
- name = cmd[1]
- return name
- return None
+ if m is None:
+ return None
+ cmd = m.group(1).split()
+ name = cmd[0]
+ if name == 'env':
+ name = cmd[1]
+ return name
clean_filename = lambda s: re.search(r"/tmp/namcap\.[0-9]*/(.*)", s).group(1)
diff --git a/namcap-tags b/namcap-tags
index 331bc15..f967724 100644
--- a/namcap-tags
+++ b/namcap-tags
@@ -16,8 +16,11 @@ dangling-symlink %s points to %s :: Symlink (%s) points to non-existing %s
directory-not-world-executable %s :: Directory (%s) does not have the world executable bit set.
elffile-in-any-package %s :: ELF file ('%s') found in an 'any' package.
elffile-not-in-allowed-dirs %s :: ELF file ('%s') outside of a valid path.
+elffile-in-questionable-dirs %s :: ELF files outside of a valid path ('%s').
elffile-with-textrel %s :: ELF file ('%s') has text relocations.
elffile-with-execstack %s :: ELF file ('%s') has executable stack.
+elffile-without-relro %s :: ELF file ('%s') lacks RELRO, check LDFLAGS.
+elffile-unstripped %s :: ELF file ('%s') is unstripped.
empty-directory %s :: Directory (%s) is empty
error-running-rule %s :: Error running rule '%s'
external-hooks-name %s :: .INSTALL file runs a command (%s) provided by hooks.
@@ -42,6 +45,7 @@ libtool-file-present %s :: File (%s) is a libtool file
library-no-package-associated %s :: Referenced library '%s' is an uninstalled dependency
link-level-dependence %s in %s :: Link-level dependence (%s) in file %s
lots-of-docs %f :: Package was %.0f%% docs by size; maybe you should split out a docs package
+makepkg-function-used %s :: PKGBUILD uses internal makepkg '%s' subroutine
missing-backup-file %s :: File in backup array (%s) not found in package
missing-description :: Missing description in PKGBUILD
missing-contributor :: Missing Contributor tag
diff --git a/parsepkgbuild.sh b/parsepkgbuild.sh
index 4df298b..12874f3 100644
--- a/parsepkgbuild.sh
+++ b/parsepkgbuild.sh
@@ -94,6 +94,11 @@ if [ -n "$source" ]; then
for i in "${source[@]}"; do echo $i; done
echo ""
fi
+if [ -n "$validpgpkeys" ]; then
+ echo "%VALIDGPGKEYS%"
+ for i in "${validpgpkeys[@]}"; do echo $i; done
+ echo ""
+fi
if [ -n "$md5sums" ]; then
echo "%MD5SUMS%"
for i in "${md5sums[@]}"; do echo $i; done