ansible-test - Verify executables are executable. (#78606)

pull/78614/head
Matt Clay 2 years ago committed by GitHub
parent 5ac40b358d
commit ece85abbc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,6 @@
bugfixes:
- ansible-test - Temporary executables are now verified as executable after creation.
Without this check, path injected scripts may not be found,
typically on systems with ``/tmp`` mounted using the "noexec" option.
This can manifest as a missing Python interpreter, or use of the wrong Python interpreter, as well
as other error conditions.

@ -33,6 +33,7 @@ from ...util import (
get_type_map, get_type_map,
remove_tree, remove_tree,
sanitize_host_name, sanitize_host_name,
verified_chmod,
) )
from ...util_common import ( from ...util_common import (
@ -166,9 +167,9 @@ class PosixCoverageHandler(CoverageHandler[PosixConfig]):
write_text_file(coverage_config_path, coverage_config, create_directories=True) write_text_file(coverage_config_path, coverage_config, create_directories=True)
os.chmod(coverage_config_path, MODE_FILE) verified_chmod(coverage_config_path, MODE_FILE)
os.mkdir(coverage_output_path) os.mkdir(coverage_output_path)
os.chmod(coverage_output_path, MODE_DIRECTORY_WRITE) verified_chmod(coverage_output_path, MODE_DIRECTORY_WRITE)
def setup_target(self): def setup_target(self):
"""Perform setup for code coverage on the target.""" """Perform setup for code coverage on the target."""

@ -683,6 +683,16 @@ def pass_vars(required: c.Collection[str], optional: c.Collection[str]) -> dict[
return env return env
def verified_chmod(path: str, mode: int) -> None:
"""Perform chmod on the specified path and then verify the permissions were applied."""
os.chmod(path, mode) # pylint: disable=ansible-bad-function
executable = any(mode & perm for perm in (stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH))
if executable and not os.access(path, os.X_OK):
raise ApplicationError(f'Path "{path}" should executable, but is not. Is the filesystem mounted with the "noexec" option?')
def remove_tree(path: str) -> None: def remove_tree(path: str) -> None:
"""Remove the specified directory, siliently continuing if the directory does not exist.""" """Remove the specified directory, siliently continuing if the directory does not exist."""
try: try:

@ -37,6 +37,7 @@ from .util import (
ApplicationError, ApplicationError,
SubprocessError, SubprocessError,
generate_name, generate_name,
verified_chmod,
) )
from .io import ( from .io import (
@ -257,9 +258,9 @@ def get_injector_path() -> str:
script = set_shebang(script, shebang) script = set_shebang(script, shebang)
write_text_file(dst, script) write_text_file(dst, script)
os.chmod(dst, mode) verified_chmod(dst, mode)
os.chmod(injector_path, MODE_DIRECTORY) verified_chmod(injector_path, MODE_DIRECTORY)
def cleanup_injector(): def cleanup_injector():
"""Remove the temporary injector directory.""" """Remove the temporary injector directory."""
@ -320,7 +321,7 @@ def get_python_path(interpreter: str) -> str:
create_interpreter_wrapper(interpreter, injected_interpreter) create_interpreter_wrapper(interpreter, injected_interpreter)
os.chmod(python_path, MODE_DIRECTORY) verified_chmod(python_path, MODE_DIRECTORY)
if not PYTHON_PATHS: if not PYTHON_PATHS:
atexit.register(cleanup_python_paths) atexit.register(cleanup_python_paths)
@ -358,7 +359,7 @@ def create_interpreter_wrapper(interpreter: str, injected_interpreter: str) -> N
write_text_file(injected_interpreter, code) write_text_file(injected_interpreter, code)
os.chmod(injected_interpreter, MODE_FILE_EXECUTE) verified_chmod(injected_interpreter, MODE_FILE_EXECUTE)
def cleanup_python_paths(): def cleanup_python_paths():

@ -21,11 +21,13 @@ class UnwantedEntry:
modules_only=False, # type: bool modules_only=False, # type: bool
names=None, # type: t.Optional[t.Tuple[str, ...]] names=None, # type: t.Optional[t.Tuple[str, ...]]
ignore_paths=None, # type: t.Optional[t.Tuple[str, ...]] ignore_paths=None, # type: t.Optional[t.Tuple[str, ...]]
ansible_test_only=False, # type: bool
): # type: (...) -> None ): # type: (...) -> None
self.alternative = alternative self.alternative = alternative
self.modules_only = modules_only self.modules_only = modules_only
self.names = set(names) if names else set() self.names = set(names) if names else set()
self.ignore_paths = ignore_paths self.ignore_paths = ignore_paths
self.ansible_test_only = ansible_test_only
def applies_to(self, path, name=None): # type: (str, t.Optional[str]) -> bool def applies_to(self, path, name=None): # type: (str, t.Optional[str]) -> bool
"""Return True if this entry applies to the given path, otherwise return False.""" """Return True if this entry applies to the given path, otherwise return False."""
@ -39,6 +41,9 @@ class UnwantedEntry:
if self.ignore_paths and any(path.endswith(ignore_path) for ignore_path in self.ignore_paths): if self.ignore_paths and any(path.endswith(ignore_path) for ignore_path in self.ignore_paths):
return False return False
if self.ansible_test_only and '/test/lib/ansible_test/_internal/' not in path:
return False
if self.modules_only: if self.modules_only:
return is_module_path(path) return is_module_path(path)
@ -114,6 +119,10 @@ class AnsibleUnwantedChecker(BaseChecker):
# see https://docs.python.org/3/library/tempfile.html#tempfile.mktemp # see https://docs.python.org/3/library/tempfile.html#tempfile.mktemp
'tempfile.mktemp': UnwantedEntry('tempfile.mkstemp'), 'tempfile.mktemp': UnwantedEntry('tempfile.mkstemp'),
# os.chmod resolves as posix.chmod
'posix.chmod': UnwantedEntry('verified_chmod',
ansible_test_only=True),
'sys.exit': UnwantedEntry('exit_json or fail_json', 'sys.exit': UnwantedEntry('exit_json or fail_json',
ignore_paths=( ignore_paths=(
'/lib/ansible/module_utils/basic.py', '/lib/ansible/module_utils/basic.py',

Loading…
Cancel
Save