From 21178617381302abb30ee4afcef72039bdca166d Mon Sep 17 00:00:00 2001 From: Milkey Mouse Date: Wed, 8 Mar 2017 22:38:37 -0800 Subject: [PATCH] Securely erase config file, fixes #2257 The SaveFile code, while ensuring atomicity, did not allow for secure erasure of the config file (containing the old encrypted key). Now it creates a hardlink to the file, lets SaveFile do its thing, and writes random data over the old file (via the hardlink). A secure erase is needed because the config file can contain the old key after changing one's password. --- src/borg/helpers.py | 10 ++++++++++ src/borg/repository.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/borg/helpers.py b/src/borg/helpers.py index dc4d34852..9c112d116 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -2256,3 +2256,13 @@ def json_dump(obj): def json_print(obj): print(json_dump(obj)) + + +def secure_erase(path): + """Attempt to securely erase a file by writing random data over it before deleting it.""" + 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()) + os.unlink(path) diff --git a/src/borg/repository.py b/src/borg/repository.py index 41d865d54..d7b84ab38 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -18,6 +18,7 @@ from .helpers import Location from .helpers import ProgressIndicatorPercent from .helpers import bin_to_hex from .helpers import yes +from .helpers import secure_erase from .locking import Lock, LockError, LockErrorT from .logger import create_logger from .lrucache import LRUCache @@ -182,9 +183,27 @@ class Repository: def save_config(self, path, config): config_path = os.path.join(path, 'config') + old_config_path = os.path.join(path, 'config.old') + + if os.path.isfile(old_config_path): + logger.warning("Old config file not securely erased on previous config update") + secure_erase(old_config_path) + + if os.path.isfile(config_path): + try: + os.link(config_path, old_config_path) + except OSError as e: + if e.errno in (errno.EMLINK, errno.EPERM): + logger.warning("Hardlink failed, cannot securely erase old config file") + else: + raise + with SaveFile(config_path) as fd: config.write(fd) + if os.path.isfile(old_config_path): + secure_erase(old_config_path) + def save_key(self, keydata): assert self.config keydata = keydata.decode('utf-8') # remote repo: msgpack issue #99, getting bytes