Browse Source

Merge pull request #3072 from ThomasWaldmann/fix-spawn-passcommand-1.0

Fix subprocess environments (1.0-maint)
TW 7 years ago
parent
commit
32d14447f5
6 changed files with 44 additions and 12 deletions
  1. 26 0
      borg/helpers.py
  2. 4 1
      borg/key.py
  3. 3 1
      borg/platform.py
  4. 3 2
      borg/platform_linux.pyx
  5. 4 6
      borg/remote.py
  6. 4 2
      borg/xattr.py

+ 26 - 0
borg/helpers.py

@@ -1563,3 +1563,29 @@ def raising_signal_handler(exc_cls):
         raise exc_cls
         raise exc_cls
 
 
     return handler
     return handler
+
+
+def prepare_subprocess_env(system, env=None):
+    """
+    Prepare the environment for a subprocess we are going to create.
+
+    :param system: True for preparing to invoke system-installed binaries,
+                   False for stuff inside the pyinstaller environment (like borg, python).
+    :param env: optionally give a environment dict here. if not given, default to os.environ.
+    :return: a modified copy of the environment
+    """
+    env = dict(env if env is not None else os.environ)
+    if system:
+        # a pyinstaller binary's bootloader modifies LD_LIBRARY_PATH=/tmp/_ME...,
+        # but we do not want that system binaries (like ssh or other) pick up
+        # (non-matching) libraries from there.
+        # thus we install the original LDLP, before pyinstaller has modified it:
+        lp_key = 'LD_LIBRARY_PATH'
+        lp_orig = env.get(lp_key + '_ORIG')  # pyinstaller >= 20160820 has this
+        if lp_orig is not None:
+            env[lp_key] = lp_orig
+    # security: do not give secrets to subprocess
+    env.pop('BORG_PASSPHRASE', None)
+    # for information, give borg version to the subprocess
+    env['BORG_VERSION'] = borg_version
+    return env

+ 4 - 1
borg/key.py

@@ -19,6 +19,7 @@ from .crypto import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_aes_blo
 from .crypto import hkdf_hmac_sha512
 from .crypto import hkdf_hmac_sha512
 from .compress import Compressor, CNONE
 from .compress import Compressor, CNONE
 from .helpers import get_limited_unpacker
 from .helpers import get_limited_unpacker
+from .helpers import prepare_subprocess_env
 
 
 
 
 PREFIX = b'\0' * 8
 PREFIX = b'\0' * 8
@@ -327,8 +328,10 @@ class Passphrase(str):
     def env_passcommand(cls, default=None):
     def env_passcommand(cls, default=None):
         passcommand = os.environ.get('BORG_PASSCOMMAND', None)
         passcommand = os.environ.get('BORG_PASSCOMMAND', None)
         if passcommand is not None:
         if passcommand is not None:
+            # passcommand is a system command (not inside pyinstaller env)
+            env = prepare_subprocess_env(system=True)
             try:
             try:
-                passphrase = subprocess.check_output(shlex.split(passcommand), universal_newlines=True)
+                passphrase = subprocess.check_output(shlex.split(passcommand), universal_newlines=True, env=env)
             except (subprocess.CalledProcessError, FileNotFoundError) as e:
             except (subprocess.CalledProcessError, FileNotFoundError) as e:
                 raise PasscommandFailure(e)
                 raise PasscommandFailure(e)
             return cls(passphrase.rstrip('\n'))
             return cls(passphrase.rstrip('\n'))

+ 3 - 1
borg/platform.py

@@ -2,6 +2,7 @@ import errno
 import os
 import os
 import subprocess
 import subprocess
 import sys
 import sys
+from .helpers import prepare_subprocess_env
 
 
 
 
 # POSIX-only, from borg 1.1 platform.base
 # POSIX-only, from borg 1.1 platform.base
@@ -20,7 +21,8 @@ def sync_dir(path):
 
 
 # most POSIX platforms (but not Linux), see also borg 1.1 platform.base
 # most POSIX platforms (but not Linux), see also borg 1.1 platform.base
 def umount(mountpoint):
 def umount(mountpoint):
-    return subprocess.call(['umount', mountpoint])
+    env = prepare_subprocess_env(system=True)
+    return subprocess.call(['umount', mountpoint], env=env)
 
 
 
 
 if sys.platform.startswith('linux'):  # pragma: linux only
 if sys.platform.startswith('linux'):  # pragma: linux only

+ 3 - 2
borg/platform_linux.pyx

@@ -2,7 +2,7 @@ import os
 import re
 import re
 import subprocess
 import subprocess
 from stat import S_ISLNK
 from stat import S_ISLNK
-from .helpers import posix_acl_use_stored_uid_gid, user2uid, group2gid, safe_decode, safe_encode
+from .helpers import posix_acl_use_stored_uid_gid, user2uid, group2gid, safe_decode, safe_encode, prepare_subprocess_env
 
 
 API_VERSION = '1.0_01'
 API_VERSION = '1.0_01'
 
 
@@ -145,4 +145,5 @@ def acl_set(path, item, numeric_owner=False):
 
 
 
 
 def umount(mountpoint):
 def umount(mountpoint):
-    return subprocess.call(['fusermount', '-u', mountpoint])
+    env = prepare_subprocess_env(system=True)
+    return subprocess.call(['fusermount', '-u', mountpoint], env=env)

+ 4 - 6
borg/remote.py

@@ -16,6 +16,7 @@ from .helpers import Error, IntegrityError, sysinfo
 from .helpers import replace_placeholders
 from .helpers import replace_placeholders
 from .helpers import BUFSIZE
 from .helpers import BUFSIZE
 from .helpers import get_limited_unpacker
 from .helpers import get_limited_unpacker
+from .helpers import prepare_subprocess_env
 from .repository import Repository
 from .repository import Repository
 from .logger import create_logger
 from .logger import create_logger
 
 
@@ -209,15 +210,12 @@ class RemoteRepository:
         self.unpacker = get_limited_unpacker('client')
         self.unpacker = get_limited_unpacker('client')
         self.p = None
         self.p = None
         testing = location.host == '__testsuite__'
         testing = location.host == '__testsuite__'
+        # when testing, we invoke and talk to a borg process directly (no ssh).
+        # when not testing, we invoke the system-installed ssh binary to talk to a remote borg.
+        env = prepare_subprocess_env(system=not testing)
         borg_cmd = self.borg_cmd(args, testing)
         borg_cmd = self.borg_cmd(args, testing)
-        env = dict(os.environ)
         if not testing:
         if not testing:
             borg_cmd = self.ssh_cmd(location) + borg_cmd
             borg_cmd = self.ssh_cmd(location) + borg_cmd
-            # pyinstaller binary adds LD_LIBRARY_PATH=/tmp/_ME... but we do not want
-            # that the system's ssh binary picks up (non-matching) libraries from there
-            env.pop('LD_LIBRARY_PATH', None)
-        env.pop('BORG_PASSPHRASE', None)  # security: do not give secrets to subprocess
-        env['BORG_VERSION'] = __version__
         logger.debug('SSH command line: %s', borg_cmd)
         logger.debug('SSH command line: %s', borg_cmd)
         self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env)
         self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env)
         self.stdin_fd = self.p.stdin.fileno()
         self.stdin_fd = self.p.stdin.fileno()

+ 4 - 2
borg/xattr.py

@@ -10,7 +10,7 @@ from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_
 from ctypes.util import find_library
 from ctypes.util import find_library
 from distutils.version import LooseVersion
 from distutils.version import LooseVersion
 
 
-from .helpers import Buffer
+from .helpers import Buffer, prepare_subprocess_env
 
 
 
 
 try:
 try:
@@ -79,7 +79,9 @@ try:
         preloads = re.split("[ :]", LD_PRELOAD)
         preloads = re.split("[ :]", LD_PRELOAD)
         for preload in preloads:
         for preload in preloads:
             if preload.startswith("libfakeroot"):
             if preload.startswith("libfakeroot"):
-                fakeroot_version = LooseVersion(subprocess.check_output(['fakeroot', '-v']).decode('ascii').split()[-1])
+                env = prepare_subprocess_env(system=True)
+                fakeroot_output = subprocess.check_output(['fakeroot', '-v'], env=env)
+                fakeroot_version = LooseVersion(fakeroot_output.decode('ascii').split()[-1])
                 if fakeroot_version >= LooseVersion("1.20.2"):
                 if fakeroot_version >= LooseVersion("1.20.2"):
                     # 1.20.2 has been confirmed to have xattr support
                     # 1.20.2 has been confirmed to have xattr support
                     # 1.18.2 has been confirmed not to have xattr support
                     # 1.18.2 has been confirmed not to have xattr support