From e3c155e75afe0a01f22bd678d8f00747383f1b19 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2016 15:41:24 +0200 Subject: [PATCH 01/14] use a clean repo to test / build the release --- docs/development.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/development.rst b/docs/development.rst index 05da98b45..480a17065 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -178,6 +178,14 @@ Checklist: git tag -s -m "tagged/signed release X.Y.Z" X.Y.Z +- create a clean repo and use it for the following steps:: + + git clone borg borg-clean + + This makes sure no uncommitted files get into the release archive. + It also will find if you forgot to commit something that is needed. + It also makes sure the vagrant machines only get committed files and + do a fresh start based on that. - run tox and/or binary builds on all supported platforms via vagrant, check for test failures - create a release on PyPi:: From e50646a07b5c4617a264107c5596d33383f90289 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2016 19:20:51 +0200 Subject: [PATCH 02/14] implement borg debug-info, fixes #1122 In tracebacks we have it already, but with the command, you can have it without a traceback also. --- borg/archiver.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/borg/archiver.py b/borg/archiver.py index 41373e259..728ece672 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -666,6 +666,11 @@ class Archiver: print("warning: %s" % e) return self.exit_code + def do_debug_info(self, args): + """display system information for debugging / bug reports""" + print(sysinfo()) + return EXIT_SUCCESS + @with_repository() def do_debug_dump_archive_items(self, args, repository, manifest, key): """dump (decrypted, decompressed) archive items metadata (not: data)""" @@ -1487,6 +1492,18 @@ class Archiver: subparser.add_argument('topic', metavar='TOPIC', type=str, nargs='?', help='additional help on TOPIC') + debug_info_epilog = textwrap.dedent(""" + This command displays some system information that might be useful for bug + reports and debugging problems. If a traceback happens, this information is + already appended at the end of the traceback. + """) + subparser = subparsers.add_parser('debug-info', parents=[common_parser], + description=self.do_debug_info.__doc__, + epilog=debug_info_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='show system infos for debugging / bug reports (debug)') + subparser.set_defaults(func=self.do_debug_info) + debug_dump_archive_items_epilog = textwrap.dedent(""" This command dumps raw (but decrypted and decompressed) archive items (only metadata) to files. """) From 2a434c3928d3145385d3ec71b6513096ee8ce1fc Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2016 19:54:40 +0200 Subject: [PATCH 03/14] skip the O_NOATIME test on GNU Hurd, fixes #1315 GNU Hurd needs to fix their O_NOATIME, after that we can re-enable the test on that platform. --- borg/testsuite/archiver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index f1f70fcdc..4591a1b23 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -362,6 +362,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): # the interesting parts of info_output2 and info_output should be same self.assert_equal(filter(info_output), filter(info_output2)) + # Search for O_NOATIME there: https://www.gnu.org/software/hurd/contributing.html - we just + # skip the test on Hurd, it is not critical anyway, just testing a performance optimization. + @pytest.mark.skipif(sys.platform == 'gnu0', reason="O_NOATIME is strangely broken on GNU Hurd") def test_atime(self): def has_noatime(some_file): atime_before = os.stat(some_file).st_atime_ns From c84ad6b7b1ffc9b6c126e73e358ee57e6706168e Mon Sep 17 00:00:00 2001 From: Martin Hostettler Date: Fri, 5 Aug 2016 22:26:59 +0200 Subject: [PATCH 04/14] Archiver.do_extract: Fix leak of downloaded chunk contents caused by preloading Include condition that path is non empty after applying strip_components into filter passed to iter_items. All filtering of files to extract must be done in the filter callable used in archive.iter_items because iter_items will preload all chunks used in items it returns. If they are not actually extracted the accumulate in the responsed dict. --- borg/archiver.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/borg/archiver.py b/borg/archiver.py index 728ece672..dc364b65b 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -371,12 +371,13 @@ class Archiver: sparse = args.sparse strip_components = args.strip_components dirs = [] - for item in archive.iter_items(lambda item: matcher.match(item[b'path']), preload=True): + filter = lambda item: matcher.match(item[b'path']) + if strip_components: + filter = lambda item: matcher.match(item[b'path']) and os.sep.join(item[b'path'].split(os.sep)[strip_components:]) + for item in archive.iter_items(filter, preload=True): orig_path = item[b'path'] if strip_components: item[b'path'] = os.sep.join(orig_path.split(os.sep)[strip_components:]) - if not item[b'path']: - continue if not args.dry_run: while dirs and not item[b'path'].startswith(dirs[-1][b'path']): dir_item = dirs.pop(-1) From 724586e965b7a61955dc5baafb092308cb8642ce Mon Sep 17 00:00:00 2001 From: Martin Hostettler Date: Tue, 9 Aug 2016 23:26:56 +0200 Subject: [PATCH 05/14] Add test for preloading releated leaks on extract with --strip-components --- borg/remote.py | 2 ++ borg/testsuite/archiver.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/borg/remote.py b/borg/remote.py index 3dda24133..6a611019c 100644 --- a/borg/remote.py +++ b/borg/remote.py @@ -203,6 +203,8 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+. raise def __del__(self): + if len(self.responses): + logging.debug("still %d cached responses left in RemoteRepository" % (len(self.responses),)) if self.p: self.close() assert False, "cleanup happened in Repository.__del__" diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index 4591a1b23..0cca1b690 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -1327,6 +1327,29 @@ class RemoteArchiverTestCase(ArchiverTestCase): def test_debug_put_get_delete_obj(self): pass + def test_strip_components_doesnt_leak(self): + self.cmd('init', self.repository_location) + self.create_regular_file('dir/file', contents=b"test file contents 123") + self.create_regular_file('dir/file2', contents=b"test file contents 345") + self.create_regular_file('skipped', contents=b"test file contents 567") + self.create_regular_file('skipped2', contents=b"test file contentsasdasd") + self.create_regular_file('skipped4', contents=b"sdfdsgdgfhttztu") + self.cmd('create', self.repository_location + '::test', 'input') + marker = 'cached responses left in RemoteRepository' + with changedir('output'): + #import rpdb2; rpdb2.start_embedded_debugger("nopass") + res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '3') + self.assert_true(marker not in res) + with self.assert_creates_file('file'): + res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '2') + self.assert_true(marker not in res) + with self.assert_creates_file('dir/file'): + res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '1') + self.assert_true(marker not in res) + with self.assert_creates_file('input/dir/file'): + res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '0') + self.assert_true(marker not in res) + def test_get_args(): archiver = Archiver() From 8268e26c6b184c736d334c1bc7c7778c082acad6 Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Wed, 17 Aug 2016 22:36:25 +0200 Subject: [PATCH 06/14] extract: refactor filter building --- borg/archiver.py | 14 +++++++++++--- borg/testsuite/archiver.py | 26 ++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/borg/archiver.py b/borg/archiver.py index dc364b65b..43ec093ad 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -343,6 +343,16 @@ class Archiver: status = '-' # dry run, item was not backed up self.print_file_status(status, path) + @staticmethod + def build_filter(matcher, strip_components=0): + if strip_components: + def item_filter(item): + return matcher.match(item[b'path']) and os.sep.join(item[b'path'].split(os.sep)[strip_components:]) + else: + def item_filter(item): + return matcher.match(item[b'path']) + return item_filter + @with_repository() @with_archive def do_extract(self, args, repository, manifest, key, archive): @@ -371,9 +381,7 @@ class Archiver: sparse = args.sparse strip_components = args.strip_components dirs = [] - filter = lambda item: matcher.match(item[b'path']) - if strip_components: - filter = lambda item: matcher.match(item[b'path']) and os.sep.join(item[b'path'].split(os.sep)[strip_components:]) + filter = self.build_filter(matcher, strip_components) for item in archive.iter_items(filter, preload=True): orig_path = item[b'path'] if strip_components: diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index 0cca1b690..db1d5c9e9 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -21,7 +21,7 @@ from ..archive import Archive, ChunkBuffer, CHUNK_MAX_EXP, flags_noatime, flags_ from ..archiver import Archiver from ..cache import Cache from ..crypto import bytes_to_long, num_aes_blocks -from ..helpers import Manifest, EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR +from ..helpers import Manifest, PatternMatcher, parse_pattern, EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR from ..remote import RemoteRepository, PathNotAllowed from ..repository import Repository from . import BaseTestCase, changedir, environment_variable @@ -1337,7 +1337,6 @@ class RemoteArchiverTestCase(ArchiverTestCase): self.cmd('create', self.repository_location + '::test', 'input') marker = 'cached responses left in RemoteRepository' with changedir('output'): - #import rpdb2; rpdb2.start_embedded_debugger("nopass") res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '3') self.assert_true(marker not in res) with self.assert_creates_file('file'): @@ -1370,3 +1369,26 @@ def test_get_args(): args = archiver.get_args(['borg', 'serve', '--restrict-to-path=/p1', '--restrict-to-path=/p2', ], 'borg init /') assert args.func == archiver.do_serve + + +class TestBuildFilter: + def test_basic(self): + matcher = PatternMatcher() + matcher.add([parse_pattern('included')], True) + filter = Archiver.build_filter(matcher) + assert filter({b'path': 'included'}) + assert filter({b'path': 'included/file'}) + assert not filter({b'path': 'something else'}) + + def test_empty(self): + matcher = PatternMatcher(fallback=True) + filter = Archiver.build_filter(matcher) + assert filter({b'path': 'anything'}) + + def test_strip_components(self): + matcher = PatternMatcher(fallback=True) + filter = Archiver.build_filter(matcher, strip_components=1) + assert not filter({b'path': 'shallow'}) + assert not filter({b'path': 'shallow/'}) # can this even happen? paths are normalized... + assert filter({b'path': 'deep enough/file'}) + assert filter({b'path': 'something/dir/file'}) From adaeb32cd4af4bc2275964e185c6b9064b204a70 Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Wed, 17 Aug 2016 22:50:38 +0200 Subject: [PATCH 07/14] Repository: fix repo not closed cleanly on InvalidRepository exception --- borg/repository.py | 1 + 1 file changed, 1 insertion(+) diff --git a/borg/repository.py b/borg/repository.py index 40d73042f..71e9040a6 100644 --- a/borg/repository.py +++ b/borg/repository.py @@ -174,6 +174,7 @@ class Repository: self.config = ConfigParser(interpolation=None) self.config.read(os.path.join(self.path, 'config')) if 'repository' not in self.config.sections() or self.config.getint('repository', 'version') != 1: + self.close() raise self.InvalidRepository(path) self.max_segment_size = self.config.getint('repository', 'max_segment_size') self.segments_per_dir = self.config.getint('repository', 'segments_per_dir') From 928f6e0ca4dc684631d0d79d039dfe9b33917f7e Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Wed, 17 Aug 2016 22:55:45 +0200 Subject: [PATCH 08/14] repository: fix spurious, empty lock.roster on InvalidRepository exception --- borg/locking.py | 7 +++++++ borg/testsuite/locking.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/borg/locking.py b/borg/locking.py index 2dbb27cbc..ff0d24845 100644 --- a/borg/locking.py +++ b/borg/locking.py @@ -201,6 +201,9 @@ class LockRoster: roster = self.load() return set(tuple(e) for e in roster.get(key, [])) + def empty(self, *keys): + return all(not self.get(key) for key in keys) + def modify(self, key, op): roster = self.load() try: @@ -293,10 +296,14 @@ class Lock: def release(self): if self.is_exclusive: self._roster.modify(EXCLUSIVE, REMOVE) + if self._roster.empty(EXCLUSIVE, SHARED): + self._roster.remove() self._lock.release() else: with self._lock: self._roster.modify(SHARED, REMOVE) + if self._roster.empty(EXCLUSIVE, SHARED): + self._roster.remove() def upgrade(self): # WARNING: if multiple read-lockers want to upgrade, it will deadlock because they diff --git a/borg/testsuite/locking.py b/borg/testsuite/locking.py index fcb21f1df..850c0ac56 100644 --- a/borg/testsuite/locking.py +++ b/borg/testsuite/locking.py @@ -64,6 +64,8 @@ class TestLock: lock2 = Lock(lockpath, exclusive=False, id=ID2).acquire() assert len(lock1._roster.get(SHARED)) == 2 assert len(lock1._roster.get(EXCLUSIVE)) == 0 + assert not lock1._roster.empty(SHARED, EXCLUSIVE) + assert lock1._roster.empty(EXCLUSIVE) lock1.release() lock2.release() @@ -71,6 +73,7 @@ class TestLock: with Lock(lockpath, exclusive=True, id=ID1) as lock: assert len(lock._roster.get(SHARED)) == 0 assert len(lock._roster.get(EXCLUSIVE)) == 1 + assert not lock._roster.empty(SHARED, EXCLUSIVE) def test_upgrade(self, lockpath): with Lock(lockpath, exclusive=False) as lock: @@ -78,6 +81,7 @@ class TestLock: lock.upgrade() # NOP assert len(lock._roster.get(SHARED)) == 0 assert len(lock._roster.get(EXCLUSIVE)) == 1 + assert not lock._roster.empty(SHARED, EXCLUSIVE) def test_downgrade(self, lockpath): with Lock(lockpath, exclusive=True) as lock: From ef13d392c7564ce7865a7642cf9ed0a57a19ce4e Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Thu, 18 Aug 2016 12:26:14 +0200 Subject: [PATCH 09/14] Cache: release lock if cache is invalid --- borg/cache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/borg/cache.py b/borg/cache.py index 0cacb2a8b..763a262aa 100644 --- a/borg/cache.py +++ b/borg/cache.py @@ -137,9 +137,11 @@ Chunk index: {0.total_unique_chunks:20d} {0.total_chunks:20d}""" cache_version = self.config.getint('cache', 'version') wanted_version = 1 if cache_version != wanted_version: + self.close() raise Exception('%s has unexpected cache version %d (wanted: %d).' % ( config_path, cache_version, wanted_version)) except configparser.NoSectionError: + self.close() raise Exception('%s does not look like a Borg cache.' % config_path) from None self.id = self.config.get('cache', 'repository') self.manifest_id = unhexlify(self.config.get('cache', 'manifest')) From b46713224bb19a1e9f13b21ff9cc38f4df6a7a9f Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Thu, 18 Aug 2016 16:23:36 +0200 Subject: [PATCH 10/14] Document DownloadPipeline.unpack_many precautions --- borg/archive.py | 9 +++++++++ borg/testsuite/archiver.py | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/borg/archive.py b/borg/archive.py index 4498c72a3..a3a133171 100644 --- a/borg/archive.py +++ b/borg/archive.py @@ -99,6 +99,15 @@ class DownloadPipeline: self.key = key def unpack_many(self, ids, filter=None, preload=False): + """ + Return iterator of items. + + *ids* is a chunk ID list of an item stream. *filter* is a callable + to decide whether an item will be yielded. *preload* preloads the data chunks of every yielded item. + + Warning: if *preload* is True then all data chunks of every yielded item have to be retrieved, + otherwise preloaded chunks will accumulate in RemoteRepository and create a memory leak. + """ unpacker = msgpack.Unpacker(use_list=False) for data in self.fetch_many(ids): unpacker.feed(data) diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index db1d5c9e9..f14edd533 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -1329,11 +1329,11 @@ class RemoteArchiverTestCase(ArchiverTestCase): def test_strip_components_doesnt_leak(self): self.cmd('init', self.repository_location) - self.create_regular_file('dir/file', contents=b"test file contents 123") - self.create_regular_file('dir/file2', contents=b"test file contents 345") - self.create_regular_file('skipped', contents=b"test file contents 567") - self.create_regular_file('skipped2', contents=b"test file contentsasdasd") - self.create_regular_file('skipped4', contents=b"sdfdsgdgfhttztu") + self.create_regular_file('dir/file', contents=b"test file contents 1") + self.create_regular_file('dir/file2', contents=b"test file contents 2") + self.create_regular_file('skipped-file1', contents=b"test file contents 3") + self.create_regular_file('skipped-file2', contents=b"test file contents 4") + self.create_regular_file('skipped-file3', contents=b"test file contents 5") self.cmd('create', self.repository_location + '::test', 'input') marker = 'cached responses left in RemoteRepository' with changedir('output'): From 2aae0b17c69f13c69db8f8c1f838328c46c7eaec Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 16 Aug 2016 00:56:52 +0200 Subject: [PATCH 11/14] update CHANGES --- docs/changes.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 7da62747a..8a75e05d5 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -57,6 +57,30 @@ Security fixes: - fix security issue with remote repository access, #1428 +Bug fixes: + +- fixed repeated LockTimeout exceptions when borg serve tried to write into + a already write-locked repo (e.g. by a borg mount), #502 part b) + This was solved by the fix for #1220 in 1.0.7rc1 already. +- fix cosmetics + file leftover for "not a valid borg repository", #1490 +- Cache: release lock if cache is invalid, #1501 +- borg extract --strip-components: fix leak of preloaded chunk contents +- Repository, when a InvalidRepository exception happens: + + - fix spurious, empty lock.roster + - fix repo not closed cleanly + +New features: + +- implement borg debug-info, fixes #1122 + (just calls already existing code via cli, same output as below tracebacks) + +Other changes: + +- skip the O_NOATIME test on GNU Hurd, fixes #1315 + (this is a very minor issue and the GNU Hurd project knows the bug) +- document using a clean repo to test / build the release + Version 1.0.7rc2 (2016-08-13) ----------------------------- From 28cbf24815649b5dcb453498dae64948abbdf411 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 2 Aug 2016 15:01:30 +0200 Subject: [PATCH 12/14] more tests for --restrict-to-path especially test that other directory names sharing same name prefix are not allowed. --- borg/testsuite/archiver.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index f14edd533..1774f347e 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -1305,12 +1305,21 @@ class RemoteArchiverTestCase(ArchiverTestCase): prefix = '__testsuite__:' def test_remote_repo_restrict_to_path(self): - self.cmd('init', self.repository_location) - path_prefix = os.path.dirname(self.repository_path) + # restricted to repo directory itself: + with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', self.repository_path]): + self.cmd('init', self.repository_location) + # restricted to repo directory itself, fail for other directories with same prefix: + with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', self.repository_path]): + self.assert_raises(PathNotAllowed, lambda: self.cmd('init', self.repository_location + '_0')) + + # restricted to a completely different path: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', '/foo']): self.assert_raises(PathNotAllowed, lambda: self.cmd('init', self.repository_location + '_1')) + path_prefix = os.path.dirname(self.repository_path) + # restrict to repo directory's parent directory: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', path_prefix]): self.cmd('init', self.repository_location + '_2') + # restrict to repo directory's parent directory and another directory: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', '/foo', '--restrict-to-path', path_prefix]): self.cmd('init', self.repository_location + '_3') From dde18d6a7660837ce7b4f30d31960bdc74252570 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 2 Aug 2016 15:50:21 +0200 Subject: [PATCH 13/14] security fix: --restrict-to-path must not accept pathes with same name prefix bug: --restrict-to-path /foo erroneously allowed /foobar. even worse: --restrict-to-path /foo/ erroneously allowed /foobar. --- borg/remote.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/borg/remote.py b/borg/remote.py index 6a611019c..472d1ac36 100644 --- a/borg/remote.py +++ b/borg/remote.py @@ -120,8 +120,13 @@ class RepositoryServer: # pragma: no cover path = path[1:] path = os.path.realpath(os.path.expanduser(path)) if self.restrict_to_paths: + # if --restrict-to-path P is given, we make sure that we only operate in/below path P. + # for the prefix check, it is important that the compared pathes both have trailing slashes, + # so that a path /foobar will NOT be accepted with --restrict-to-path /foo option. + path_with_sep = os.path.join(path, '') # make sure there is a trailing slash (os.sep) for restrict_to_path in self.restrict_to_paths: - if path.startswith(os.path.realpath(restrict_to_path)): + restrict_to_path_with_sep = os.path.join(os.path.realpath(restrict_to_path), '') # trailing slash + if path_with_sep.startswith(restrict_to_path_with_sep): break else: raise PathNotAllowed(path) From f32c8858ad3f6637fca35ef814f6cd584d1cc658 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 18 Aug 2016 23:05:58 +0200 Subject: [PATCH 14/14] update CHANGES with description of issue #1428 --- docs/changes.rst | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 8a75e05d5..debf4feb6 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -50,12 +50,29 @@ The best check that everything is ok is to run a dry-run extraction:: borg extract -v --dry-run REPO::ARCHIVE -Version 1.0.7 (not released yet) --------------------------------- +Version 1.0.7 (2016-08-19) +-------------------------- Security fixes: -- fix security issue with remote repository access, #1428 +- borg serve: fix security issue with remote repository access, #1428 + If you used e.g. --restrict-to-path /path/client1/ (with or without trailing + slash does not make a difference), it acted like a path prefix match using + /path/client1 (note the missing trailing slash) - the code then also allowed + working in e.g. /path/client13 or /path/client1000. + + As this could accidentally lead to major security/privacy issues depending on + the pathes you use, the behaviour was changed to be a strict directory match. + That means --restrict-to-path /path/client1 (with or without trailing slash + does not make a difference) now uses /path/client1/ internally (note the + trailing slash here!) for matching and allows precisely that path AND any + path below it. So, /path/client1 is allowed, /path/client1/repo1 is allowed, + but not /path/client13 or /path/client1000. + + If you willingly used the undocumented (dangerous) previous behaviour, you + may need to rearrange your --restrict-to-path pathes now. We are sorry if + that causes work for you, but we did not want a potentially dangerous + behaviour in the software (not even using a for-backwards-compat option). Bug fixes: