diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-06-19 22:27:30 +0200 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-06-20 14:12:23 +0200 |
| commit | 2c7757048169be7db306efbedacd3ae9af4fbb64 (patch) | |
| tree | d99ca0662167caa3e8e8c946ecb65f0bffcca867 /python | |
| parent | 8f3e63d2ac2a29365733fc0e85e8f53e412cb5d1 (diff) | |
| download | vyos-1x-2c7757048169be7db306efbedacd3ae9af4fbb64.tar.gz vyos-1x-2c7757048169be7db306efbedacd3ae9af4fbb64.zip | |
utils: T9003: add list-argument variant of cmd() for safer subprocess execution
A list-argument variant of cmd() for safer subprocess execution named cmdl().
Command must be a list of strings; no shell interpolation is performed, which
eliminates a class of command-injection risks present when building commands
with f-strings or other string formatting.
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/utils/process.py | 44 |
1 files changed, 44 insertions, 0 deletions
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py index 28027684c..3e5576925 100644 --- a/python/vyos/utils/process.py +++ b/python/vyos/utils/process.py @@ -210,6 +210,50 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None, raise raising(feedback) return decoded +def cmdl(command: list[str], flag: str = '', input: str | bytes | None = None, + timeout: float | None = None, env: dict[str, str] | None = None, + stdout: int = PIPE, stderr: int = PIPE, + raising: type[Exception] | None = None, message: str = '', + expect: list[int] | None = None, vrf: str | None = None, + netns: str | None = None, sudo: bool = False) -> str: + """ + A list-argument variant of cmd() for safer subprocess execution. + + command must be a list of strings; no shell interpolation is performed, + which eliminates a class of command-injection risks present when building + commands with f-strings or other string formatting. + + sudo: prepend sudo(8) to the command for privileged execution + """ + if not isinstance(command, list): + raise TypeError(f'cmdl() requires a list, got {type(command).__name__}') + if expect is None: + expect = [0] + if sudo: + command = ['sudo'] + command + decoded, code = popen( + command, flag, + shell=False, + input=input, timeout=timeout, + env=env, + stdout=stdout, stderr=stderr, + decode='utf-8', + vrf=vrf, netns=netns, + ) + if code not in expect: + wrapper = get_wrapper(vrf, netns) + cmd_str = shlex.join(wrapper + command) + feedback = message + '\n' if message else '' + feedback += f'failed to run command: {cmd_str}\n' + feedback += f'returned: {decoded}\n' + feedback += f'exit code: {code}' + if raising is None: + raise OSError(code, feedback) + else: + raise raising(feedback) + return decoded + + def rc_cmd(command, flag='', shell=None, input=None, timeout=None, env=None, stdout=PIPE, stderr=STDOUT, decode='utf-8', vrf=None, netns=None, buffered=True): |
