Преглед на файлове

give clean error msg for invalid nonce file, see #7967

That rather long traceback was not pretty.

Of course users should usually have valid nonce files,
but multiple users managed to corrupt the file contents somehow.

Also:
- add Error / ErrorWithTraceback exception classes to RPC layer.
- add hex_to_bin helper
- also call hex_to_bin for the local nonce file (security dir)
Thomas Waldmann преди 1 година
родител
ревизия
d9132a34d4
променени са 4 файла, в които са добавени 36 реда и са изтрити 9 реда
  1. 9 3
      src/borg/crypto/nonces.py
  2. 13 2
      src/borg/helpers/parseformat.py
  3. 6 2
      src/borg/remote.py
  4. 8 2
      src/borg/repository.py

+ 9 - 3
src/borg/crypto/nonces.py

@@ -1,9 +1,9 @@
 import os
 import sys
-from binascii import unhexlify
 
+from ..helpers import Error
 from ..helpers import get_security_dir
-from ..helpers import bin_to_hex
+from ..helpers import bin_to_hex, hex_to_bin
 from ..platform import SaveFile
 from ..remote import InvalidRPCMethod
 
@@ -23,9 +23,15 @@ class NonceManager:
     def get_local_free_nonce(self):
         try:
             with open(self.nonce_file) as fd:
-                return bytes_to_long(unhexlify(fd.read()))
+                nonce_hex = fd.read().strip()
         except FileNotFoundError:
             return None
+        else:
+            try:
+                nonce_bytes = hex_to_bin(nonce_hex, length=8)
+            except ValueError as e:
+                raise Error(f"Local security dir has an invalid nonce file: {e}") from None
+            return bytes_to_long(nonce_bytes)
 
     def commit_local_nonce_reservation(self, next_unreserved, start_nonce):
         if self.get_local_free_nonce() != start_nonce:

+ 13 - 2
src/borg/helpers/parseformat.py

@@ -1,4 +1,5 @@
 import argparse
+import binascii
 import hashlib
 import json
 import os
@@ -8,7 +9,6 @@ import shlex
 import socket
 import stat
 import uuid
-from binascii import hexlify
 from collections import Counter, OrderedDict
 from datetime import datetime, timezone
 from functools import partial
@@ -27,7 +27,18 @@ from ..platformflags import is_win32
 
 
 def bin_to_hex(binary):
-    return hexlify(binary).decode('ascii')
+    return binascii.hexlify(binary).decode('ascii')
+
+
+def hex_to_bin(hex, length=None):
+    try:
+        binary = binascii.unhexlify(hex)
+        binary_len = len(binary)
+        if length is not None and binary_len != length:
+            raise ValueError(f"Expected {length} bytes ({2 * length} hex digits), got {binary_len} bytes.")
+    except binascii.Error as e:
+        raise ValueError(str(e)) from None
+    return binary
 
 
 def safe_decode(s, coding='utf-8', errors='surrogateescape'):

+ 6 - 2
src/borg/remote.py

@@ -18,7 +18,7 @@ from subprocess import Popen, PIPE
 from . import __version__
 from .compress import Compressor
 from .constants import *  # NOQA
-from .helpers import Error, IntegrityError
+from .helpers import Error, ErrorWithTraceback, IntegrityError
 from .helpers import bin_to_hex
 from .helpers import get_base_dir
 from .helpers import get_limited_unpacker
@@ -747,7 +747,11 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
             old_server = b'exception_args' not in unpacked
             args = unpacked.get(b'exception_args')
 
-            if error == 'DoesNotExist':
+            if error == 'Error':
+                raise Error(args[0].decode())
+            elif error == 'ErrorWithTraceback':
+                raise ErrorWithTraceback(args[0].decode())
+            elif error == 'DoesNotExist':
                 raise Repository.DoesNotExist(self.location.processed)
             elif error == 'AlreadyExists':
                 raise Repository.AlreadyExists(self.location.processed)

+ 8 - 2
src/borg/repository.py

@@ -16,7 +16,7 @@ from .hashindex import NSIndex
 from .helpers import Error, ErrorWithTraceback, IntegrityError, format_file_size, parse_file_size
 from .helpers import Location
 from .helpers import ProgressIndicatorPercent
-from .helpers import bin_to_hex
+from .helpers import bin_to_hex, hex_to_bin
 from .helpers import secure_erase, safe_unlink
 from .helpers import Manifest
 from .helpers import msgpack
@@ -363,9 +363,15 @@ class Repository:
         nonce_path = os.path.join(self.path, 'nonce')
         try:
             with open(nonce_path) as fd:
-                return int.from_bytes(unhexlify(fd.read()), byteorder='big')
+                nonce_hex = fd.read().strip()
         except FileNotFoundError:
             return None
+        else:
+            try:
+                nonce_bytes = hex_to_bin(nonce_hex, length=8)
+            except ValueError as e:
+                raise Error(f"Repository has an invalid nonce file: {e}") from None
+            return int.from_bytes(nonce_bytes, byteorder='big')
 
     def commit_nonce_reservation(self, next_unreserved, start_nonce):
         if self.do_lock and not self.lock.got_exclusive_lock():