From ba1f8926cc79b1b29ef1123250418a46a7df4992 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 13 Jun 2022 15:47:50 +0200 Subject: [PATCH] secure_erase: avoid collateral damage, fixes #6768 if a hardlink copy of a repo was made and a new repo config shall be saved, do NOT fill in random garbage before deleting the previous repo config, because that would damage the hardlink copy. --- src/borg/helpers/fs.py | 22 ++++++++++++++++------ src/borg/repository.py | 4 ++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/borg/helpers/fs.py b/src/borg/helpers/fs.py index d1a412da0..f6a45c896 100644 --- a/src/borg/helpers/fs.py +++ b/src/borg/helpers/fs.py @@ -189,13 +189,23 @@ def scandir_inorder(*, path, fd=None): return sorted(os.scandir(arg), key=scandir_keyfunc) -def secure_erase(path): - """Attempt to securely erase a file by writing random data over it before deleting it.""" +def secure_erase(path, *, avoid_collateral_damage): + """Attempt to securely erase a file by writing random data over it before deleting it. + + If avoid_collateral_damage is True, we only secure erase if the total link count is 1, + otherwise we just do a normal "delete" (unlink) without first overwriting it with random. + This avoids other hardlinks pointing to same inode as getting damaged, but might be less secure. + A typical scenario where this is useful are quick "hardlink copies" of bigger directories. + + If avoid_collateral_damage is False, we always secure erase. + If there are hardlinks pointing to the same inode as , they will contain random garbage afterwards. + """ with open(path, 'r+b') as fd: - length = os.stat(fd.fileno()).st_size - fd.write(os.urandom(length)) - fd.flush() - os.fsync(fd.fileno()) + st = os.stat(fd.fileno()) + if not (st.st_nlink > 1 and avoid_collateral_damage): + fd.write(os.urandom(st.st_size)) + fd.flush() + os.fsync(fd.fileno()) os.unlink(path) diff --git a/src/borg/repository.py b/src/borg/repository.py index c6565cdaa..b86954216 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -306,7 +306,7 @@ def save_config(self, path, config): if os.path.isfile(old_config_path): logger.warning("Old config file not securely erased on previous config update") - secure_erase(old_config_path) + secure_erase(old_config_path, avoid_collateral_damage=True) if os.path.isfile(config_path): link_error_msg = ("Failed to securely erase old repository config file (hardlinks not supported). " @@ -333,7 +333,7 @@ def save_config(self, path, config): "read-only repositories." % (e.strerror, e.filename)) if os.path.isfile(old_config_path): - secure_erase(old_config_path) + secure_erase(old_config_path, avoid_collateral_damage=True) def save_key(self, keydata): assert self.config