diff -Nru bzr-2.7.0/debian/changelog bzr-2.7.0/debian/changelog --- bzr-2.7.0/debian/changelog 2016-02-11 15:46:53.000000000 +0000 +++ bzr-2.7.0/debian/changelog 2017-08-29 05:05:07.000000000 +0000 @@ -1,3 +1,28 @@ +bzr (2.7.0-2ubuntu3.1) xenial-security; urgency=medium + + * SECURITY UPDATE: Possible arbitrary code execution on clients + through malicious bzr+ssh URLs + - debian/patches/24_ssh_hostnames-lp1710979: ensure that host + arguments to ssh cannot be treated as ssh options. + - LP: #1710979 + + -- Steve Beattie Mon, 28 Aug 2017 22:04:57 -0700 + +bzr (2.7.0-2ubuntu3) xenial; urgency=medium + + * Fix http Basic auth with credentials longer than ~57 characters + (LP: #1606203). + + -- Colin Watson Fri, 30 Sep 2016 11:58:13 +0100 + +bzr (2.7.0-2ubuntu2) xenial-proposed; urgency=medium + + * SRU: LP: #1592731. + * Cope with paramiko making argument to SFTPFile.prefetch() mandatory. + Closes: #822116 + + -- Matthias Klose Wed, 15 Jun 2016 11:42:01 +0200 + bzr (2.7.0-2ubuntu1) xenial; urgency=low * Resynchronise on Debian (LP: #1544487). Remaining changes: diff -Nru bzr-2.7.0/debian/patches/16_paramiko_compat bzr-2.7.0/debian/patches/16_paramiko_compat --- bzr-2.7.0/debian/patches/16_paramiko_compat 1970-01-01 00:00:00.000000000 +0000 +++ bzr-2.7.0/debian/patches/16_paramiko_compat 2016-06-15 09:45:40.000000000 +0000 @@ -0,0 +1,16 @@ +=== modified file 'bzrlib/transport/sftp.py' +Index: unstable/bzrlib/transport/sftp.py +=================================================================== +--- unstable.orig/bzrlib/transport/sftp.py ++++ unstable/bzrlib/transport/sftp.py +@@ -410,8 +410,9 @@ class SFTPTransport(ConnectedTransport): + try: + path = self._remote_path(relpath) + f = self._get_sftp().file(path, mode='rb') ++ size = f.stat().st_size + if self._do_prefetch and (getattr(f, 'prefetch', None) is not None): +- f.prefetch() ++ f.prefetch(size) + return f + except (IOError, paramiko.SSHException), e: + self._translate_io_exception(e, path, ': error retrieving', diff -Nru bzr-2.7.0/debian/patches/19_fix_long_creds bzr-2.7.0/debian/patches/19_fix_long_creds --- bzr-2.7.0/debian/patches/19_fix_long_creds 1970-01-01 00:00:00.000000000 +0000 +++ bzr-2.7.0/debian/patches/19_fix_long_creds 2016-09-09 14:00:49.000000000 +0000 @@ -0,0 +1,49 @@ +Description: http Basic auth was broken + When a long (>57) user/pass combination was used, a spurious '\n' ended up in the header value, crashing httplib. +. +Author: Vincent Ladeuil + + + +=== modified file 'bzrlib/tests/test_http.py' +--- unstable.orig/bzrlib/tests/test_http.py 2016-02-01 18:06:32 +0000 ++++ unstable/bzrlib/tests/test_http.py 2016-09-09 12:57:44 +0000 +@@ -260,6 +260,16 @@ + self.assertEqual('basic', scheme) + self.assertEqual('realm="Thou should not pass"', remainder) + ++ def test_build_basic_header_with_long_creds(self): ++ handler = _urllib2_wrappers.BasicAuthHandler() ++ user = 'user' * 10 # length 40 ++ password = 'password' * 5 # length 40 ++ header = handler.build_auth_header( ++ dict(user=user, password=password), None) ++ # https://bugs.launchpad.net/bzr/+bug/1606203 was caused by incorrectly ++ # creating a header value with an embedded '\n' ++ self.assertFalse('\n' in header) ++ + def test_basic_extract_realm(self): + scheme, remainder = self.parse_header( + 'Basic realm="Thou should not pass"', + +=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py' +--- unstable.orig/bzrlib/transport/http/_urllib2_wrappers.py 2016-01-31 12:55:31 +0000 ++++ unstable/bzrlib/transport/http/_urllib2_wrappers.py 2016-09-09 12:58:12 +0000 +@@ -48,6 +48,7 @@ + # actual code more or less do that, tests should be written to + # ensure that. + ++import base64 + import errno + import httplib + import os +@@ -1491,7 +1492,7 @@ + + def build_auth_header(self, auth, request): + raw = '%s:%s' % (auth['user'], auth['password']) +- auth_header = 'Basic ' + raw.encode('base64').strip() ++ auth_header = 'Basic ' + base64.b64encode(raw) + return auth_header + + def extract_realm(self, header_value): + diff -Nru bzr-2.7.0/debian/patches/24_ssh_hostnames-lp1710979 bzr-2.7.0/debian/patches/24_ssh_hostnames-lp1710979 --- bzr-2.7.0/debian/patches/24_ssh_hostnames-lp1710979 1970-01-01 00:00:00.000000000 +0000 +++ bzr-2.7.0/debian/patches/24_ssh_hostnames-lp1710979 2017-08-29 05:04:12.000000000 +0000 @@ -0,0 +1,166 @@ +Description: Refuse to connect to ssh hostnames starting with a dash. Fixes LP:1710979 +Author: Jelmer Vernooij +Origin: commit, Revision ID: jelmer@jelmer.uk-20170819145828-qk2p7qlg5j2fbsiz + +* Security fix: hostnames starting with a dash in bzr+ssh URLs + are now filtered out when using a subprocess SSH client. + . + Thanks to Augie Fackler for reporting. + (Jelmer Vernooij, #1710979) + + +=== modified file 'bzrlib/tests/test_ssh_transport.py' +--- + bzrlib/tests/test_ssh_transport.py | 38 ++++++++++++++++++++++++++++++++++++- + bzrlib/transport/ssh.py | 16 +++++++++++++-- + 2 files changed, 51 insertions(+), 3 deletions(-) + +Index: b/bzrlib/tests/test_ssh_transport.py +=================================================================== +--- a/bzrlib/tests/test_ssh_transport.py ++++ b/bzrlib/tests/test_ssh_transport.py +@@ -22,6 +22,7 @@ from bzrlib.transport.ssh import ( + SSHCorpSubprocessVendor, + LSHSubprocessVendor, + SSHVendorManager, ++ StrangeHostname, + ) + + +@@ -161,6 +162,19 @@ class SSHVendorManagerTests(TestCase): + + class SubprocessVendorsTests(TestCase): + ++ def test_openssh_command_tricked(self): ++ vendor = OpenSSHSubprocessVendor() ++ self.assertEqual( ++ vendor._get_vendor_specific_argv( ++ "user", "-oProxyCommand=blah", 100, command=["bzr"]), ++ ["ssh", "-oForwardX11=no", "-oForwardAgent=no", ++ "-oClearAllForwardings=yes", ++ "-oNoHostAuthenticationForLocalhost=yes", ++ "-p", "100", ++ "-l", "user", ++ "--", ++ "-oProxyCommand=blah", "bzr"]) ++ + def test_openssh_command_arguments(self): + vendor = OpenSSHSubprocessVendor() + self.assertEqual( +@@ -171,6 +185,7 @@ class SubprocessVendorsTests(TestCase): + "-oNoHostAuthenticationForLocalhost=yes", + "-p", "100", + "-l", "user", ++ "--", + "host", "bzr"] + ) + +@@ -184,9 +199,16 @@ class SubprocessVendorsTests(TestCase): + "-oNoHostAuthenticationForLocalhost=yes", + "-p", "100", + "-l", "user", +- "-s", "host", "sftp"] ++ "-s", "--", "host", "sftp"] + ) + ++ def test_openssh_command_tricked(self): ++ vendor = SSHCorpSubprocessVendor() ++ self.assertRaises( ++ StrangeHostname, ++ vendor._get_vendor_specific_argv, ++ "user", "-oProxyCommand=host", 100, command=["bzr"]) ++ + def test_sshcorp_command_arguments(self): + vendor = SSHCorpSubprocessVendor() + self.assertEqual( +@@ -209,6 +231,13 @@ class SubprocessVendorsTests(TestCase): + "-s", "sftp", "host"] + ) + ++ def test_lsh_command_tricked(self): ++ vendor = LSHSubprocessVendor() ++ self.assertRaises( ++ StrangeHostname, ++ vendor._get_vendor_specific_argv, ++ "user", "-oProxyCommand=host", 100, command=["bzr"]) ++ + def test_lsh_command_arguments(self): + vendor = LSHSubprocessVendor() + self.assertEqual( +@@ -231,6 +260,13 @@ class SubprocessVendorsTests(TestCase): + "--subsystem", "sftp", "host"] + ) + ++ def test_plink_command_tricked(self): ++ vendor = PLinkSubprocessVendor() ++ self.assertRaises( ++ StrangeHostname, ++ vendor._get_vendor_specific_argv, ++ "user", "-oProxyCommand=host", 100, command=["bzr"]) ++ + def test_plink_command_arguments(self): + vendor = PLinkSubprocessVendor() + self.assertEqual( +Index: b/bzrlib/transport/ssh.py +=================================================================== +--- a/bzrlib/transport/ssh.py ++++ b/bzrlib/transport/ssh.py +@@ -46,6 +46,10 @@ else: + from paramiko.sftp_client import SFTPClient + + ++class StrangeHostname(errors.BzrError): ++ _fmt = "Refusing to connect to strange SSH hostname %(hostname)s" ++ ++ + SYSTEM_HOSTKEYS = {} + BZR_HOSTKEYS = {} + +@@ -360,6 +364,11 @@ class SubprocessVendor(SSHVendor): + # tests, but beware of using PIPE which may hang due to not being read. + _stderr_target = None + ++ @staticmethod ++ def _check_hostname(arg): ++ if arg.startswith('-'): ++ raise StrangeHostname(hostname=arg) ++ + def _connect(self, argv): + # Attempt to make a socketpair to use as stdin/stdout for the SSH + # subprocess. We prefer sockets to pipes because they support +@@ -424,9 +433,9 @@ class OpenSSHSubprocessVendor(Subprocess + if username is not None: + args.extend(['-l', username]) + if subsystem is not None: +- args.extend(['-s', host, subsystem]) ++ args.extend(['-s', '--', host, subsystem]) + else: +- args.extend([host] + command) ++ args.extend(['--', host] + command) + return args + + register_ssh_vendor('openssh', OpenSSHSubprocessVendor()) +@@ -439,6 +448,7 @@ class SSHCorpSubprocessVendor(Subprocess + + def _get_vendor_specific_argv(self, username, host, port, subsystem=None, + command=None): ++ self._check_hostname(host) + args = [self.executable_path, '-x'] + if port is not None: + args.extend(['-p', str(port)]) +@@ -460,6 +470,7 @@ class LSHSubprocessVendor(SubprocessVend + + def _get_vendor_specific_argv(self, username, host, port, subsystem=None, + command=None): ++ self._check_hostname(host) + args = [self.executable_path] + if port is not None: + args.extend(['-p', str(port)]) +@@ -481,6 +492,7 @@ class PLinkSubprocessVendor(SubprocessVe + + def _get_vendor_specific_argv(self, username, host, port, subsystem=None, + command=None): ++ self._check_hostname(host) + args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch'] + if port is not None: + args.extend(['-P', str(port)]) diff -Nru bzr-2.7.0/debian/patches/series bzr-2.7.0/debian/patches/series --- bzr-2.7.0/debian/patches/series 2016-02-10 19:41:18.000000000 +0000 +++ bzr-2.7.0/debian/patches/series 2017-08-29 05:04:12.000000000 +0000 @@ -2,3 +2,6 @@ 03_spurious_test_failure 07_shorten_test_names 13_spurious_test_failure +16_paramiko_compat +19_fix_long_creds +24_ssh_hostnames-lp1710979