瀏覽代碼

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 os
 import sys
 import sys
-from binascii import unhexlify
 
 
+from ..helpers import Error
 from ..helpers import get_security_dir
 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 ..platform import SaveFile
 from ..remote import InvalidRPCMethod
 from ..remote import InvalidRPCMethod
 
 
@@ -23,9 +23,15 @@ class NonceManager:
     def get_local_free_nonce(self):
     def get_local_free_nonce(self):
         try:
         try:
             with open(self.nonce_file) as fd:
             with open(self.nonce_file) as fd:
-                return bytes_to_long(unhexlify(fd.read()))
+                nonce_hex = fd.read().strip()
         except FileNotFoundError:
         except FileNotFoundError:
             return None
             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):
     def commit_local_nonce_reservation(self, next_unreserved, start_nonce):
         if self.get_local_free_nonce() != start_nonce:
         if self.get_local_free_nonce() != start_nonce:

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

@@ -1,4 +1,5 @@
 import argparse
 import argparse
+import binascii
 import hashlib
 import hashlib
 import json
 import json
 import os
 import os
@@ -8,7 +9,6 @@ import shlex
 import socket
 import socket
 import stat
 import stat
 import uuid
 import uuid
-from binascii import hexlify
 from collections import Counter, OrderedDict
 from collections import Counter, OrderedDict
 from datetime import datetime, timezone
 from datetime import datetime, timezone
 from functools import partial
 from functools import partial
@@ -27,7 +27,18 @@ from ..platformflags import is_win32
 
 
 
 
 def bin_to_hex(binary):
 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'):
 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 . import __version__
 from .compress import Compressor
 from .compress import Compressor
 from .constants import *  # NOQA
 from .constants import *  # NOQA
-from .helpers import Error, IntegrityError
+from .helpers import Error, ErrorWithTraceback, IntegrityError
 from .helpers import bin_to_hex
 from .helpers import bin_to_hex
 from .helpers import get_base_dir
 from .helpers import get_base_dir
 from .helpers import get_limited_unpacker
 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
             old_server = b'exception_args' not in unpacked
             args = unpacked.get(b'exception_args')
             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)
                 raise Repository.DoesNotExist(self.location.processed)
             elif error == 'AlreadyExists':
             elif error == 'AlreadyExists':
                 raise Repository.AlreadyExists(self.location.processed)
                 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 Error, ErrorWithTraceback, IntegrityError, format_file_size, parse_file_size
 from .helpers import Location
 from .helpers import Location
 from .helpers import ProgressIndicatorPercent
 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 secure_erase, safe_unlink
 from .helpers import Manifest
 from .helpers import Manifest
 from .helpers import msgpack
 from .helpers import msgpack
@@ -363,9 +363,15 @@ class Repository:
         nonce_path = os.path.join(self.path, 'nonce')
         nonce_path = os.path.join(self.path, 'nonce')
         try:
         try:
             with open(nonce_path) as fd:
             with open(nonce_path) as fd:
-                return int.from_bytes(unhexlify(fd.read()), byteorder='big')
+                nonce_hex = fd.read().strip()
         except FileNotFoundError:
         except FileNotFoundError:
             return None
             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):
     def commit_nonce_reservation(self, next_unreserved, start_nonce):
         if self.do_lock and not self.lock.got_exclusive_lock():
         if self.do_lock and not self.lock.got_exclusive_lock():