From d35d388d9ce9cd183da3ee8551228c70bbf88d36 Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Thu, 25 May 2017 16:13:40 +0200 Subject: [PATCH] cache integrity: handle interference from old versions --- src/borg/cache.py | 20 ++++++++++++++++---- src/borg/testsuite/archiver.py | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/borg/cache.py b/src/borg/cache.py index aad48ca5f..9af9dbf97 100644 --- a/src/borg/cache.py +++ b/src/borg/cache.py @@ -240,6 +240,7 @@ def create(self): config.set('cache', 'repository', self.repository.id_str) config.set('cache', 'manifest', '') config.add_section('integrity') + config.set('integrity', 'manifest', '') with SaveFile(self.config_path) as fd: config.write(fd) @@ -256,11 +257,19 @@ def load(self): self.manifest_id = unhexlify(self._config.get('cache', 'manifest')) self.timestamp = self._config.get('cache', 'timestamp', fallback=None) self.key_type = self._config.get('cache', 'key_type', fallback=None) - if not self._config.has_section('integrity'): - self._config.add_section('integrity') try: self.integrity = dict(self._config.items('integrity')) + if self._config.get('cache', 'manifest') != self.integrity.pop('manifest'): + # The cache config file is updated (parsed with ConfigParser, the state of the ConfigParser + # is modified and then written out.), not re-created. + # Thus, older versions will leave our [integrity] section alone, making the section's data invalid. + # Therefore, we also add the manifest ID to this section and + # can discern whether an older version interfere by comparing the manifest IDs of this section + # and the main [cache] section. + self.integrity = {} + logger.warning('Cache integrity data lost: old Borg version modified the cache.') except configparser.NoSectionError: + logger.debug('Cache integrity: No integrity data found (files, chunks). Cache is from old version.') self.integrity = {} previous_location = self._config.get('cache', 'previous_location', fallback=None) if previous_location: @@ -272,11 +281,14 @@ def save(self, manifest=None, key=None): if manifest: self._config.set('cache', 'manifest', manifest.id_str) self._config.set('cache', 'timestamp', manifest.timestamp) + if not self._config.has_section('integrity'): + self._config.add_section('integrity') + for file, integrity_data in self.integrity.items(): + self._config.set('integrity', file, integrity_data) + self._config.set('integrity', 'manifest', manifest.id_str) if key: self._config.set('cache', 'key_type', str(key.TYPE)) self._config.set('cache', 'previous_location', self.repository._location.canonical_path()) - for file, integrity_data in self.integrity.items(): - self._config.set('integrity', file, integrity_data) with SaveFile(self.config_path) as fd: self._config.write(fd) diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 13c24106e..a2d0ff806 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -2949,6 +2949,23 @@ def test_chunks_archive(self): assert 'Cached archive chunk index of test1 is corrupted' in out assert 'Fetching and building archive index for test1' in out + def test_old_version_intefered(self): + self.create_test_files() + self.cmd('init', '--encryption=repokey', self.repository_location) + cache_path = json.loads(self.cmd('info', self.repository_location, '--json'))['cache']['path'] + + # Modify the main manifest ID without touching the manifest ID in the integrity section. + # This happens if a version without integrity checking modifies the cache. + config_path = os.path.join(cache_path, 'config') + config = ConfigParser(interpolation=None) + config.read(config_path) + config.set('cache', 'manifest', bin_to_hex(bytes(32))) + with open(config_path, 'w') as fd: + config.write(fd) + + out = self.cmd('info', self.repository_location) + assert 'Cache integrity data lost: old Borg version modified the cache.' in out + class DiffArchiverTestCase(ArchiverTestCaseBase): def test_basic_functionality(self):