From 4aebb8d936487ed96fda96b5de24349c1f3f0e88 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sat, 8 Aug 2020 23:15:18 +0200 Subject: [PATCH] Test shebang lines Executable scripts must have shebang (#!) line to be effectively executable on most Unix-like systems. Enforce this, and conversely enforce that files with a shebang line are executable. Check that the specified interperter is consistent with the file extension. Signed-off-by: Gilles Peskine --- tests/scripts/check_files.py | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/scripts/check_files.py b/tests/scripts/check_files.py index c498117a9..09ab615ab 100755 --- a/tests/scripts/check_files.py +++ b/tests/scripts/check_files.py @@ -172,6 +172,53 @@ class PermissionIssueTracker(FileIssueTracker): self.files_with_issues[filepath] = None +class ShebangIssueTracker(FileIssueTracker): + """Track files with a bad, missing or extraneous shebang line. + + Executable scripts must start with a valid shebang (#!) line. + """ + + heading = "Invalid shebang line:" + + # Allow either /bin/sh, /bin/bash, or /usr/bin/env. + # Allow at most one argument (this is a Linux limitation). + # For sh and bash, the argument if present must be options. + # For env, the argument must be the base name of the interpeter. + _shebang_re = re.compile(rb'^#! ?(?:/bin/(bash|sh)(?: -[^\n ]*)?' + rb'|/usr/bin/env ([^\n /]+))$') + _extensions = { + b'bash': 'sh', + b'perl': 'pl', + b'python3': 'py', + b'sh': 'sh', + } + + def is_valid_shebang(self, first_line, filepath): + m = re.match(self._shebang_re, first_line) + if not m: + return False + interpreter = m.group(1) or m.group(2) + if interpreter not in self._extensions: + return False + if not filepath.endswith('.' + self._extensions[interpreter]): + return False + return True + + def check_file_for_issue(self, filepath): + is_executable = os.access(filepath, os.X_OK) + with open(filepath, "rb") as f: + first_line = f.readline() + if first_line.startswith(b'#!'): + if not is_executable: + # Shebang on a non-executable file + self.files_with_issues[filepath] = None + elif not self.is_valid_shebang(first_line, filepath): + self.files_with_issues[filepath] = [1] + elif is_executable: + # Executable without a shebang + self.files_with_issues[filepath] = None + + class EndOfFileNewlineIssueTracker(FileIssueTracker): """Track files that end with an incomplete line (no newline character at the end of the last line).""" @@ -292,6 +339,7 @@ class IntegrityChecker: self.setup_logger(log_file) self.issues_to_check = [ PermissionIssueTracker(), + ShebangIssueTracker(), EndOfFileNewlineIssueTracker(), Utf8BomIssueTracker(), UnixLineEndingIssueTracker(),