2
0
Эх сурвалжийг харах

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.
Milkey Mouse 8 жил өмнө
parent
commit
2117861738

+ 10 - 0
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)

+ 19 - 0
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