Skip to content

Commit bfc56a9

Browse files
committed
Use shlex.quote to prevent interrupting chroot path and shell expand out of chroot
Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com>
1 parent 7c852c7 commit bfc56a9

File tree

3 files changed

+22
-7
lines changed

3 files changed

+22
-7
lines changed

exec_helpers/_ssh_client_base.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,20 @@ def keepalive(self, enforce: bool = True) -> "typing.ContextManager[None]":
399399
"""
400400
return _KeepAliveContext(ssh=self, enforce=enforce)
401401

402+
def _prepare_command(self, cmd: str, chroot_path: typing.Optional[str] = None) -> str:
403+
"""Prepare command: cower chroot and other cases.
404+
405+
:param cmd: main command
406+
:param chroot_path: path to make chroot for execution
407+
:returns: final command, includes chroot, if required
408+
"""
409+
if not self.sudo_mode:
410+
return super()._prepare_command(cmd=cmd, chroot_path=chroot_path)
411+
if any((chroot_path, self._chroot_path)):
412+
target_path: str = shlex.quote(chroot_path if chroot_path else self._chroot_path)
413+
return f'chroot {target_path} sudo sh -c "eval {shlex.quote(cmd)}"'
414+
return f'sudo -S sh -c "eval {shlex.quote(cmd)}"'
415+
402416
# noinspection PyMethodOverriding
403417
def _execute_async( # pylint: disable=arguments-differ
404418
self,
@@ -479,7 +493,6 @@ def _execute_async( # pylint: disable=arguments-differ
479493

480494
started = datetime.datetime.utcnow()
481495
if self.sudo_mode:
482-
cmd = f'sudo -S bash -c "eval {shlex.quote(cmd)}"'
483496
chan.exec_command(cmd) # nosec # Sanitize on caller side
484497
if stdout.channel.closed is False:
485498
# noinspection PyTypeChecker

exec_helpers/api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import datetime
2727
import logging
2828
import re
29+
import shlex
2930
import threading
3031
import typing
3132
import warnings
@@ -227,7 +228,8 @@ def _prepare_command(self, cmd: str, chroot_path: typing.Optional[str] = None) -
227228
:returns: final command, includes chroot, if required
228229
"""
229230
if any((chroot_path, self._chroot_path)):
230-
return f"chroot {chroot_path if chroot_path else self._chroot_path} {cmd}"
231+
target_path: str = shlex.quote(chroot_path if chroot_path else self._chroot_path)
232+
return f'chroot {target_path} sh -c "eval {shlex.quote(cmd)}"'
231233
return cmd
232234

233235
def execute_async( # pylint: disable=missing-param-doc,differing-param-doc,differing-type-doc

test/test_ssh_client_execute_async_special.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def test_001_execute_async_sudo(ssh, ssh_transport_channel):
120120
ssh_transport_channel.assert_has_calls(
121121
(
122122
mock.call.makefile_stderr("rb"),
123-
mock.call.exec_command(f'sudo -S bash -c \"eval {shlex.quote(cmd_execute)}\"'),
123+
mock.call.exec_command(f'sudo -S sh -c \"eval {shlex.quote(command)}\"\n'),
124124
)
125125
)
126126

@@ -133,7 +133,7 @@ def test_002_execute_async_with_sudo_enforce(ssh, ssh_transport_channel):
133133
ssh_transport_channel.assert_has_calls(
134134
(
135135
mock.call.makefile_stderr("rb"),
136-
mock.call.exec_command(f'sudo -S bash -c \"eval {shlex.quote(cmd_execute)}\"'),
136+
mock.call.exec_command(f'sudo -S sh -c \"eval {shlex.quote(command)}\"\n'),
137137
)
138138
)
139139

@@ -163,7 +163,7 @@ def test_005_execute_async_sudo_password(ssh, ssh_transport_channel, mocker):
163163
ssh_transport_channel.assert_has_calls(
164164
(
165165
mock.call.makefile_stderr("rb"),
166-
mock.call.exec_command(f'sudo -S bash -c \"eval {shlex.quote(cmd_execute)}\"'),
166+
mock.call.exec_command(f'sudo -S sh -c \"eval {shlex.quote(command)}\"\n'),
167167
)
168168
)
169169

@@ -233,7 +233,7 @@ def test_011_execute_async_chroot_cmd(ssh, ssh_transport_channel):
233233
ssh_transport_channel.assert_has_calls(
234234
(
235235
mock.call.makefile_stderr("rb"),
236-
mock.call.exec_command(f'chroot / {command}\n'),
236+
mock.call.exec_command(f'chroot / sh -c "eval {shlex.quote(command)}"\n'),
237237
)
238238
)
239239

@@ -245,7 +245,7 @@ def test_012_execute_async_chroot_context(ssh, ssh_transport_channel):
245245
ssh_transport_channel.assert_has_calls(
246246
(
247247
mock.call.makefile_stderr("rb"),
248-
mock.call.exec_command(f'chroot / {command}\n'),
248+
mock.call.exec_command(f'chroot / sh -c "eval {shlex.quote(command)}"\n'),
249249
)
250250
)
251251

0 commit comments

Comments
 (0)