Browse Source

backup/restore: add option to use uncommon compression filter anyway

Previous commit introduced protection against uncommon (potentially
malicious) compression filters. This breaks restoring backups made with
a custom compression filter. Add an option to override this check, by
naming compression filter to use explicitly.
Marek Marczykowski-Górecki 4 years ago
parent
commit
14f77860bf

+ 7 - 0
doc/manpages/qvm-backup-restore.rst

@@ -72,6 +72,13 @@ Options
     this limit and restore such (broken, or potentially malicious) backup
     anyway.
 
+.. option:: --compression-filter, -Z
+
+    Force specific compression filter, instead of the one named in the backup
+    header. The compression filter is a command that accepts ``-d`` option to
+    decompress data on stdin and output it to stdout.  This can be used to
+    override built-in protection against uncommon compression.
+
 .. option:: --dest-vm=APPVM, -d APPVM
 
     Restore from a backup located in a specific AppVM

+ 14 - 2
qubesadmin/backup/restore.py

@@ -192,6 +192,11 @@ class BackupHeader(object):
             else:
                 raise QubesException("Unrecognized header type")
             if not header.validator(value):
+                if key == 'compression-filter':
+                    raise QubesException(
+                        "Unusual compression filter '{f}' found. Use "
+                        "--compression-filter={f} to use it anyway.".format(
+                            f=value))
                 raise QubesException("Invalid value for header: {}".format(key))
             setattr(self, header.field, value)
 
@@ -902,7 +907,8 @@ class BackupRestore(object):
                 self.subdir = subdir
                 self.username = os.path.basename(subdir)
 
-    def __init__(self, app, backup_location, backup_vm, passphrase):
+    def __init__(self, app, backup_location, backup_vm, passphrase,
+                 force_compression_filter=None):
         super(BackupRestore, self).__init__()
 
         #: qubes.Qubes instance
@@ -919,6 +925,10 @@ class BackupRestore(object):
         #: backup path, inside VM pointed by :py:attr:`backup_vm`
         self.backup_location = backup_location
 
+        #: force using specific application for (de)compression, instead of
+        #: the one named in the backup header
+        self.force_compression_filter = force_compression_filter
+
         #: passphrase protecting backup integrity and optionally decryption
         self.passphrase = passphrase
 
@@ -1252,7 +1262,9 @@ class BackupRestore(object):
                     "failed). Is the password correct?")
             filename = os.path.join(self.tmpdir, filename)
             with open(filename, 'rb') as f_header:
-                header_data = BackupHeader(f_header.read())
+                header_data = BackupHeader(
+                    f_header.read(),
+                    compression_filter=self.force_compression_filter)
             os.unlink(filename)
 
         return header_data

+ 4 - 2
qubesadmin/tests/tools/qvm_backup_restore.py

@@ -58,7 +58,8 @@ class TC_00_qvm_backup_restore(qubesadmin.tests.QubesTestCase):
             mock_handle_broken.assert_called_once_with(
                 self.app, mock.ANY, mock_restore_info)
         mock_backup.assert_called_once_with(
-            self.app, '/some/path', None, 'testpass')
+            self.app, '/some/path', None, 'testpass',
+            force_compression_filter=None)
         self.assertAllCalled()
 
     @mock.patch('qubesadmin.tools.qvm_backup_restore.input', create=True)
@@ -92,7 +93,8 @@ class TC_00_qvm_backup_restore(qubesadmin.tests.QubesTestCase):
         qubesadmin.tools.qvm_backup_restore.main(['/some/path', 'test-vm'],
             app=self.app)
         mock_backup.assert_called_once_with(
-            self.app, '/some/path', None, 'testpass')
+            self.app, '/some/path', None, 'testpass',
+            force_compression_filter=None)
         self.assertEqual(mock_backup.return_value.options.exclude, ['test-vm2'])
         self.assertAllCalled()
 

+ 7 - 1
qubesadmin/tools/qvm_backup_restore.py

@@ -72,6 +72,11 @@ parser.add_argument("--ignore-size-limit", action="store_true",
     dest="ignore_size_limit", default=False,
     help="Ignore size limit calculated from backup metadata")
 
+parser.add_argument("--compression-filter", "-Z", action="store",
+    dest="compression",
+    help="Force specific compression filter program, "
+         "instead of the one from the backup header")
+
 parser.add_argument("-d", "--dest-vm", action="store", dest="appvm",
     help="Specify VM containing the backup to be restored")
 
@@ -213,7 +218,8 @@ def main(args=None, app=None):
 
     try:
         backup = BackupRestore(args.app, args.backup_location,
-            appvm, passphrase)
+            appvm, passphrase,
+            force_compression_filter=args.compression)
     except qubesadmin.exc.QubesException as e:
         parser.error_runtime(str(e))
         # unreachable - error_runtime will raise SystemExit