Coder Social home page Coder Social logo

v6ak / qubes-incremental-backup-poc Goto Github PK

View Code? Open in Web Editor NEW
9.0 6.0 2.0 82 KB

proof of concept of incremental backup scheme for Qubes

Home Page: https://groups.google.com/d/msgid/qubes-users/901b82dc-f781-4c13-ad00-33b4337fc84a%40googlegroups.com

Python 90.31% Shell 9.30% Makefile 0.39%
qubes-os backup duplicity-backup incremental-backups backups

qubes-incremental-backup-poc's Introduction

Status

Proof of concept. Backup format details will likely change.

Currently, it is public preview. It contains scripts that can:

  • Install the backup storage VM (see install-backup-storage-vm).
  • Run the backup from dom0 (see backup).

You might notice that DVM is also involved in the backup process, but no installation script is there. That's OK: All scripts needed in the DVM are uploaded automatically after DVM creation.

It is needed to configure manually:

  • dom0: Write name of BackupStorageVM to ~/.v6-qubes-backup-poc/backup-storage-vm-name
  • BackupStorageVM

Goals

  • Incremental backups
  • Minimal backup size
  • Reasonably exploit-resistant. More specificaly:
    • If filesystem of one VM is maliciously crafted, it does not affect other VMs or their backups, even in case of fs driver bug. And of course, it does not affect dom0 at all.
    • All operations in dom0 are very careful about untrusted inputs, including master config, which is likely to be downloaded from an untrusted place.
  • Backup will be transfered over network to some untrusted place.
  • Attacker can obtain some reasonably limited metadata about the backup.
  • If attacker has modified the backup, it can be detected. Replay attacks (i.e., replacing by some older backup) are not currently intended to be mitigated, though. Attacker that controls the storage can also mix age of backups, remove some VM or add a removed VM.
    • IDEA: Add timestamps to detect replay attacks.
    • IDEA: Use Merkle-tree-based commit mechanism to authenticate whole backups, not just their parts. This would also prevent some other atomicity-related bugs in backup backend.
  • bonus: Ability to backup a running VM (if easily achievable)

How to achieve it

  • File-based backup with exclusions of directories like ~/.cache. This allows small backup size and incremental backups.
  • Backup is performed of cloned VM images (shapshots on LVM). This allows repairing damaged FS before mounting. It also allows using backup on LVM shapshots while running.
  • Backups are both encrypted and authenticated using GPG.
  • I just write some scripts that glue some existing software for easy use with Qubes.

Where to run backup

There are several place where one could run a backup:

  • dom0
  • Original VM
  • One dedicated backup VM
  • DVM

The dom0 might sound as an obvious choice, because it has enough access to all data. But this is a wrong way once we need to parse some untrusted filesystem. While dom0 needs to be involved, parsing untrusted filesystem in dom0 is a no-go.

The original VM might also sound as a reasonable choice, because it has access to all the data it needs. There are some minor (though non-negligible) security concerns like VM being able to read and modify its own backups. (This is mostly an issue when handling ransomware, but this is now what I am trying to strongly defend.) But there are also some practical issues. First is non-atomicity of reading the filesystem while the VM is running. For example, when a file is moved during backup, the backup can contain the file twice, once, or it might not contain the file at all. Second practical issue is: Should we shut the VM down after backing up? Maybe we should, because the user does not want it to be running. Maybe we should not, because the user is using the VM. Not shutting all VMs will cause OOM soon on a typical Qubes setup. Shutting all the VMs would close user's work sooner or later. Leaving previous state would be a good heuristic, but still not perfect. Most notably, user can start using a VM while backing up.

Using one dedicated backup VM is no-go for similar reasons to dom0. The VM would be powerful (have plaintext access to content of other VMs) and exposed to attacks from crafted filesystem at the same time.

The remaining option is a DVM, one per VM. This DVM would have access to a clone of filesystem of the relevant VM. If the DVM is compromised through a maliciously crafted filesystem, it does not have access to other VMs. Such exploit would have similar impact as compromised VM that takes care about its own backup. The need of exploiting a bug in filesystem parsing code is a serious mitigation factor, though. This also can allow using the VM when performing backup.

The disadvantage of usage of DVM is that we will probably have to use a different approach for dom0 backups. But dom0 is so special case…

What software?

I've tried few backup tools. I have decided to use Duplicity. This one was somehow usable and looks to be decently designed:

  • Opensource (=> publicly auditable)
  • Performs file-based incremental backups (full backup is performed once per a defined interval)
  • Backup is encrypted and authenticated using GPG. When one tampers the backup, it should be caught by GPG and the restore procedure should not continue. (I haven't checked if/how they defend against renaming the files or against copying the files under another name. However, such vulnerability would be probably hard to abuse, especially when you cannot cross VMs.)
  • Supports various storage backends.
  • Writing a custom storage backend seems to be a realistic amount of work: https://github.com/Rudd-O/duplicity-qubes
  • GPG can be configured. It can use either symmetric (preferred) or asymmetric cryptography.

I anecdoticaly remember some issues with Duplicity, probably related with interrupted backup process. Maybe we should be careful there, but I don't know a good alternative for Duplicity at the moment.

I haven't performed a deep review of Duplicity.

If you want to use something else, I hope it will not be hard to replace it with some other suitable software. It looks like replacing Duplicity by some other software would require minor (if any) change to the dom0 part.

Keys for backups

I believe it is clear that we need different keys for different VMs, unless we move the encryption and decryption to dom0. All keys are derived from one passphrase:

master_key = stretch_key(passphrase)
subkey_$vm = derive_subkey(master_key, "vm-"+vm)

We also need few other subkeys, but they aren't so important.

Key stretching is intentionally applied only once. Applying it for each VM separately would increase computational cost when running backup for multiple VMs, but it would not make legitimate users more secure against guessing the password. After guessing the password on one VM's backup, attacker would be able to reuse it on other VMs.

Now, we have to define those two functions. Those two functions are meaned as configurable, so one could change them later. (The current implementation might not allow such degree of configurability, though.)

Key stretching

Why we need this: Key size for encryption is usually enough, but passphrase is usually not as strong. If you use random characters from base64 without padding, you would need to remember 22 characters for equivalent of 128-bit keys or 44 characters for equivalent of 256-bit keys. Moreover, passphrases can be weakened by shouldersurfing. After observing several characters, attacker can try to guess others.

I have picked scrypt, because it is tunable and fairly reviewed. Other functions (like bcrypt) could be also considered there.

Parameters for scrypt are inspired from [https://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors] . I have considered increasing parallelization parameter, but the implementation of scrypt I have used does not look like being able to use multiple cores. But we still might want to increate p and decrease N in order to lower memory requirements. The link suggests as a more paranoid version something like this:

stretch_key(passphrase) = scrypt(passphrase, salt, 1<<20, 8, 1, 32)

This requires 1GiB of RAM for scrypt, which is much memory in some cases. Since dom0 can have about 1GiB–4GiB of RAM, depending on requirements of other VMs, it can be a significant portion of total RAM available. I propose decresaing amount of needed memory with similar CPU requirements (similar time without parallelisation, lower memory requirements):

stretch_key(passphrase) = scrypt(passphrase, salt, 1<<17, 8, 8, 32)

Note that all the parameters are more strict than the soft variant recommended in the post mentioned above. While the recommendation is from 2009, the parameters are increased more than the Moore's inflation as of 2017. (In 8 years, computers are expected to be 2^4 times faster, while this is 2^6 times more hard. Moreover, the Moore's inflation does not hold for some parameters like RAM latency, which can slow the progress down, especially with Scrypt design.)

Another advantage of this approach is that it can be faster for legitimate user when the implementation adds support for parallelism. Note that higher parallelisation does not help attacker when trying large number of passphrases, because it is ambarassimgly parallel task. That is, attacker can use multiple cores even for p=1. Sure, attacker needs fewer memory that with the former parameters. But that's the cost of making it nicer (requiring less memory) for legitimate users. And still, the attacker needs 128MiB of RAM, which is probably enough.

The size 32 [bytes] was chosen because we can hardly go (or need to go) beyond 256-bit security.

Subkeys for VMs

The subkey generation was designed to be fast. Key is already stretched:

derive_subkey(master_key, subkey_identifier) = hmac(sha256, master_key, subkey_identifier)

I know HMAC was designed for a different purpose. We need a keyed-PRF rather than MAC. But according to https://cseweb.ucsd.edu/~mihir/papers/hmac-new.html, “HMAC is a PRF under the sole assumption that the compression function is a PRF”.

TODO: Is SHA256 really a PRF? I hope so. And it also seems that it is used in such way in TLS: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol

Probably even some punk solution like sha256(master_key || vm_name) would work well (length-extension attack is not practically applicable there), but I would preffer a more standard way.

The sha256 function was chosen because of its output size length. Again, we can hardly go (or need to go) beyond 256-bit security.

File names

It is tempting to use filenames simply derived from VM name. This would leak some metadata, though. The amount of leaked metadata is not huge, but still noticable. I argue that we should go on the safe side and obscure the metadata in some reasonable way.

We use some kind of deterministic encryption. I have used a scheme similar to AES-SIV (which I have found no suitable implementation for), but I use HMAC-SHA256 instead of S2V. HMAC is simpler, already implemented and should do the same job there. The scheme (I call it AES-HIV) does not support AAD, but this is not a requirement.

VM image cloning

VM image is cloned before backup in order to see consistently a single state. The type of snapshot depends on the storage type of the VM. Two storage types are supported: plain file and LVM device.

Plain files are simply copied. A check that the VM is not running is performed before the copy process is started. However, when the copy is in progress, the VM can be started, which can lead to some inconsistent copy. Yes, the situation is not nice, but I suppose that file-backed VMs aren't going to be there a long time, because Qubes 4 will reportedly use LVM.

LVM block devices are handled by CoW snapshot. This is much faster and it works in some way even if the VM is running. Well… Actually, if you use a filesystem designed and configured for surviving sudden power outage, it should work well, because the snapshot of mounted filesystem will look like a filesystem where sudden power loss has happened. Practically speaking, if you are using ext4, you should have journal enabled and you shouldn't use flags like data=writeback.

Note that Qubes 3.2 does not officially support LVM for private VM images. It is expected that private.img is a symlink to /dev/<vg>/<lv>. Other formats like /dev/mapper/<vg-with-doubled-minuses>-<lv-with-doubled-minuses> are not supported.

dom0 backup

TODO

Limitations

  • DVM template is somehow trusted.
  • Works with some (most?) Duplicity backends. It requires allowing directory-like URLs and ability to create a non-existent directory. Works at least with file backend (without any modification) and rsync backend (thanks to a hack).
  • We assume that DVM template has drivers for all filesystems we need to backup
  • VM config backup is missing. User needs to backup ~/.v6-qubes-backup-poc/master in order to be able to restore the backup.
  • The implementation assumes that attacker cannot read parameters of running applications (e.g., via /proc). This is justifiable in Qubes security model, especially in dom0, but it is not very nice.
  • VMs are identified by name. If you destroy a VM and create a VM of the same name then, it will use the same keys and it might have some security implications.
  • We strongly assume that attacker does not have any access to dom0. More specificaly, attacker cannot obtain a RAM snapshot of dom0 and attacker cannot list running applications of dom0 (e.g., via /proc). While those assumptions are standard in Qubes security model, failing to satisfy them can cause the backup security to break hardly. No serious attempt has been made to reduce number of copies of sensitive cryptographic material in RAM.
  • It assumes that we use locale utf-8. When one switches locale, it can cause issues, mostly with password. If you use another encoding, you might get troubles, especially if your password contains some characters that are represented differently in your encoding than in utf-8.
  • It assumes that user does not run the script multiple times in parallel. Various race conditions (cloned images, config files) could happen there.
  • limitation
  • (Probably incomplete)

Human aspects for password

TODO:

qubes-incremental-backup-poc's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

cyrinux azeret6

qubes-incremental-backup-poc's Issues

Review usage of utf-8

Two different encodings are used, ASCII and UTF-8. ASCII is much simpler, but UTF-8 might be needed in some cases (e.g. passphrase with national characters).

When UTF-8 is parsed from untrusted source, it adds some attack surface. So, I should review if UTF-8 is used only when not controlled by attacked. (I believe so, but it should be still verified.)

Implement backup script for dom0

  • clone image
    • standard img
    • LVM
  • start DVM
  • cleanup DVM
  • cleanup cloned image
  • cleanup cloned image in case of crash
  • ask for password
  • derive master key
  • derive subkey for VM
  • derive subkeys for filenames
  • check for bad password
  • run backup script
  • handle config

Reconsider use of check_output for VM calls

VM call can return huge amount of data, DoSing the dom0. In case of a wrong implementation (which seems to be limited to just wrong buffer operation, so not so likely), it could even overflow.

Derive password for storage backend from master passphrase

This is a challenging task.

We could use passphrase to derive password directly. But this would skip the master secret derivation, essentially bypassing all custom-configured password-stretching parameters. This is bad in long term, as this does not allow to use better key-stretching parameters in future without breaking compatibility. It also cannot be salted by anything else than storage URL and username. Salting with storage URL and username has some drawbacks (mostly the need of exactly same URL and username, even if the backend tolerates some deviation like case), but they are probably justifiable.

We could also download some public data from the backup storage (this can hardly be storage-agnostic) to get key derivation parameters. Those key stretching parameters have to be considered as untrusted. This implies:

  • If we don't include passphrase_test in public parameters for key derivation, the backup storage can perform downgrade attack*: It can provide weak key stretching parameters and then bruteforce the password. (Rainbow attacks can be avoided, though: We can add URL and username to the salt.)
  • We can mitigate the downgrade attack by including passphrase_test in public parameters for key derivation. However, if we include passphrase_test in the public parameters for key derivation, anyone (not just the backup storage administrators) can download it and bruteforce master password offline.

Another disadvantage: This can increase practical value of shouldersurfing attacks.

However, maybe the hassle with design and implementation and all the risks are simply not worth of the enhancement.

*) Also anyone who can attack the connection can do this. So, the connection to backup storage is a new weak point.

Use various BDVMs

Qubes 4.0 will be able to create a DVM from any AppVM. By default, we should use DVM of the template the VM belongs to.

Disconnect the BDVM from network

The DVM that performs backup (BDVM) needs no network access. According to principle of least privileges, it should not have it.

Threats

  • A VM with restricted (e.g., Torified) network access (=Attacking VM, AVM) creates a malformed filesystem on its private partition. The BDVM has some vulnerability in filesystem driver. As a result, AVM is able to execute arbitrary code in BDVM. Since the BDVM can be connected to Internet, AVM gets direct access to the Internet. This can lead to deanonymization.

Advantages

If BDVM had no direct access to the Internet, the adversary would not be able to get the Internet access and deanonymize the user this way. However, advantage of BDVM without Internet access is somewhat limited there. If adversary has an access to the backup storage, she can deanonymize the user anyway. Offloading encryption from BDVM could help partially, but attacker still would be able to observe backup sizes.

Restore: VM with proper name should proper when finished

Currently, VM is created with final name and then restore is performed. If restore fails, some partially restored VM with good name exists.

When a VM with a correct name appears, it should have correct content.

Proposed behavior:

  1. Create VM with temporary name
  2. Disable backups for the VM
  3. Restore
  4. Enable backups for the VM
  5. Rename the temporary VM

Elaborate: Global exclude lists

When ignoring some files, it would be useful to ignore them in:

a. all VMs
b. in all VMs based on a particular template

It would be useful to have a global exclude lists that could do this instead of hardcoding such files to scripts.

Use Merkle-tree-based storage

Why?

  • Backups can be authenticated as whole, not just individual VMs (at best).
  • Atomicity: When computer crashes during backup, partial backup does not cause problems.
  • It can obscure what file belongs to what VM. (But data usage patterns can still leak it, at least to some degree.)
  • Allows buffering volumes. (Better performance, can potentially obscure data access patterns when upload is reordered.)
  • Allow some data checks on remote. It can check hashes, because they are keys.

BackupStorageVM<->dom0 interface

Rather a simple key-value interface:

  • get/set KDF params (operates with unauthenticated plaintext and simple KDF params format)
  • getRoot – returns signed root with timestamp
  • setRoot (or maybe compareAndSetRoot) – atomically stores a new signed root
  • getBlob – returns an encrypted blob by ciphertext hash
  • putBlob – uploads a new blob
  • deleteBlob – removes blob

dom0 <-> BDVM interface

The interface should be very similar to BackupStorageVM<->dom0, but dom0 has to verify the permissions and maybe handle encryption.

Directory structure

Directory structure would be implemented on top of the mentioned key-value storage as Merkle tree.

What to decide

  • How to handle permissions to particular files without leaking any data?
  • How to buffer files in the target VM without leaking any data when maliciously modified? (When buffering, one has to send unauthenticated chunks.) Do we want this at all?
  • How to handle garbage collection? Maybe it will require parsing and traversing directory structure in dom0. OTOH, it should be a rather simple format.
  • When user removes a VM and creates a VM with the same name, should they be related in any way?

Consider backup backends

Currently, we use Duplicity. The reason is not that it was carefuly chosen as the best one. The reason is I have some experience with it, despite I chose it in past for quite a different scenario. So, I am collecting info about backup backends in order to decide well: https://docs.google.com/spreadsheets/d/1rUXn8VkR5nrrtDhywKBpNu2zuTHzOHDX6F053ynBSjw/edit?usp=sharing

Legend for features:

  • Green feature: great
  • Red feature: deal-breaker
  • Orange feature: somewhat suboptimal
  • Blue feature: not yet known
  • Grey feature: not evaluated because the backend DNF (it has some serious issue mentioned in another column)

Legend for first column:

  • Green backend: usable
  • Orange backend: some suspicion it is not usable
  • Red backend: DNF

What do we want:

  • Don't reflect files on input as files on backup directly. This leaks too much metadata and might have too high demand on storage backend (atributtes, filenames, …)
  • Compression is good for reducing data volume, but it is also a potential side channel. One should be able to turn it off.
  • Encryption and authentication is useful in short term. In long term, we might want to use a Merkle tree in order to authenticate all the data together, so we will probably move responsibility for encryption and authentication off the backup backend. Nevertheless, I still have briefly evaluated encryption and authentication for the short term. I have not verified backup-volume-level malleability, because this does not look like a feasible attack surface.
  • We want an easy way to add a custom storage backend. It should require implementing as few methods as possible. The more it gets complex, the harder it will be to integrate.

We will want at least one file-based backend and one block-based backend (qvm-backup or similar).

If you can fill in some missing info or suggest another great backend, write it here, please!

Rewrite cryptopunk

Crypto is currently handled by openssl CLI. (Except for scrypt, which calls Perl.) See cryptopunk.py file. This is a high-latency solution that makes some assumptions (attacker can't read /proc, which is justifiable in dom0) that I would like to get rid off.

Maybe the best available alternative would be nacl/sodium. For Python, we could use python3-pynacl or python3-libnacl. But I can't see those packages in Fedora 23. If Qubes 4.0 updates dom0 to Fedora 24 or newer (not sure), we can go this way. Until then, I'd wait. Or maybe someone will suggest a better solution that we can use without waiting for Qubes 4.0.

Add checks for backups

Various checks can be considered:

a. Backup can be restored without errors.
b. Compare data from backup and real system. (Challenge: exclusions.)
c. Perform some user-defined VM-specific test.

Make the code more asynchronous

The main benefit I can see is starting the DVM while entering the password.

  • Study Python parallelism model (and memory model)
  • Study Python Futures
  • Make it async where reasonable

Store basic metadata with each subbackup

e.g. master password salt, bcrypt parameters etc.

Those metadata are public and should be kept with each backup. It allows the backup to be restorable without any further metadata.

Logging in BackupStorageVM

Currently, we set logger to “some logger”, which is something nonexistent. Think what could be done better.

Also, we do some independent logging to /tmp, which is just a quick hack.

Elaborate file names

We should hide VM names from filenames. Options:

a. Encrypt.
b. Hash.
c. Create a table.

Maybe it would be handy if we have direct access. This disqualifies encrypted names with explicit IVs.

Hashes obscure VM name length (which is not the only way to obscure it, though), but are impractical if you cannot enumerate the VM names./

Encrypted file with table seems to be rather hard approach.

Restore on clean system is convenient

Long-term metatask.

Target scenario:

  1. Install Qubes.
  2. Install this backup system to dom0. (Even better: If this tool is integrated into Qubes, then this step would be unneeded.)
  3. Create and install backup-storage VM for your backup backend. This might require installing some software to corresponding backup backend (e.g., Duplicity), but it should be as straightforward as possible.
  4. (Maybe as a part of step 3.) Configure backup URL and credentials.
  5. Run one simple command to restore it all. Minimum interaction is required from user at this moment. Or choose what to restore now and restore the rest later.

Results of the procedure:

  • All VMs (AppVMs, TemplateVMs, HVMs, …) are restored.
  • All VM config (firewall routes, VM drive size, …)
  • User config of dom0 is restored. (Challenge, since we are restoring to a running system.)
  • TODO: specify behavior for dom0 drivers etc. Maybe those should be optional, as one might want to perform restore on different hardware. Also, in some cases, drivers might be prerequisities of even starting the restore process.
  • TODO: Specify behavior for volume layout. It might be useful to be preserved in some cases (e.g., laptop repaired), but this might be undesirable in other cases (e.g., completely new laptop).

TODO: list relevant issues.

Sync in more universal way

Calling sync in VM's shell is not much universal. Theoretically, ti can also cause something completely different. So we should use some RPC endpoint for FS sync when it is implemented.

This is followup of #13.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.