From 6e355131670ad014e45f879475ddf800f0080d41 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 23 Feb 2022 12:35:04 -0800 Subject: Make 'fscrypt setup' offer a choice of directory modes World-writable directories are not appropriate for some systems, so offer a choice of single-user-writable and world-writable modes, with single-user-writable being the default. Add a new documentation section to help users decide which one to use. --- README.md | 53 ++++++++++++++++++++++++++--- actions/context_test.go | 3 +- cli-tests/run.sh | 4 +-- cli-tests/t_encrypt.out | 18 ++++++---- cli-tests/t_encrypt_custom.out | 9 +++-- cli-tests/t_encrypt_login.out | 42 +++++++++++++++-------- cli-tests/t_encrypt_raw_key.out | 15 ++++++--- cli-tests/t_metadata.out | 6 ++-- cli-tests/t_not_supported.sh | 2 +- cli-tests/t_setup.out | 6 ++-- cli-tests/t_setup.sh | 4 +-- cli-tests/t_single_user.out | 30 +++++++++++++++++ cli-tests/t_single_user.sh | 55 ++++++++++++++++++++++++++++++ cli-tests/t_status.out | 6 ++-- cli-tests/t_v1_policy.out | 3 +- cmd/fscrypt/commands.go | 6 ++-- cmd/fscrypt/errors.go | 4 +++ cmd/fscrypt/flags.go | 14 ++++++-- cmd/fscrypt/setup.go | 41 +++++++++++++++++++++-- cmd/fscrypt/status.go | 11 +++++- filesystem/filesystem.go | 74 +++++++++++++++++++++++++++++++++++------ filesystem/filesystem_test.go | 35 +++++++++++++++++-- 22 files changed, 374 insertions(+), 67 deletions(-) create mode 100644 cli-tests/t_single_user.out create mode 100755 cli-tests/t_single_user.sh diff --git a/README.md b/README.md index 75b3d62..26fd084 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ dependencies](#runtime-dependencies). - [Building and installing](#building-and-installing) - [Runtime dependencies](#runtime-dependencies) - [Configuration file](#configuration-file) +- [Setting up `fscrypt` on a filesystem](#setting-up-fscrypt-on-a-filesystem) - [Setting up for login protectors](#setting-up-for-login-protectors) - [Securing your login passphrase](#securing-your-login-passphrase) - [Enabling the PAM module](#enabling-the-pam-module) @@ -377,6 +378,44 @@ The fields are: kernels, it's better to not use this setting and instead (re-)create your encrypted directories with `"policy_version": "2"`. +## Setting up `fscrypt` on a filesystem + +`fscrypt` needs some directories to exist on the filesystem on which encryption +will be used: + +* `MOUNTPOINT/.fscrypt/policies` +* `MOUNTPOINT/.fscrypt/protectors` + +(If login protectors are used, these must also exist on the root filesystem.) + +To create these directories, run `fscrypt setup MOUNTPOINT`. If MOUNTPOINT is +owned by root, as is usually the case, then this command will require root. + +There will be one decision you'll need to make: whether non-root users will be +allowed to create `fscrypt` metadata (policies and protectors). + +If you say `y`, then these directories will be made world-writable, with the +sticky bit set so that users can't delete each other's files -- just like +`/tmp`. If you say `N`, then these directories will be writable only by root. + +Saying `y` maximizes the usability of `fscrypt`, and on most systems it's fine +to say `y`. However, on some systems this may be inappropriate, as it will +allow malicious users to fill the entire filesystem unless filesystem quotas +have been configured -- similar to problems that have historically existed with +other world-writable directories, e.g. `/tmp`. If you are concerned about this, +say `N`. If you say `N`, then you'll only be able to run `fscrypt` as root to +set up encryption on users' behalf, unless you manually set custom permissions +on the metadata directories to grant write access to specific users or groups. + +If you chose the wrong mode at `fscrypt setup` time, you can change the +directory permissions at any time. To enable single-user writable mode, run: + + sudo chmod 0755 MOUNTPOINT/.fscrypt/* + +To enable world-writable mode, run: + + sudo chmod 1777 MOUNTPOINT/.fscrypt/* + ## Setting up for login protectors If you want any encrypted directories to be protected by your login passphrase, @@ -646,11 +685,15 @@ MOUNTPOINT DEVICE FILESYSTEM ENCRYPTION FSCRYPT Defaulting to policy_version 2 because kernel supports it. Customizing passphrase hashing difficulty for this system... Created global config file at "/etc/fscrypt.conf". -Metadata directories created at "/.fscrypt". +Allow users other than root to create fscrypt metadata on the root filesystem? +(See https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] y +Metadata directories created at "/.fscrypt", writable by everyone. # Start using fscrypt with our filesystem ->>>>> fscrypt setup /mnt/disk -Metadata directories created at "/mnt/disk/.fscrypt". +>>>>> sudo fscrypt setup /mnt/disk +Allow users other than root to create fscrypt metadata on this filesystem? (See +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] y +Metadata directories created at "/mnt/disk/.fscrypt", writable by everyone. # Initialize encryption on a new empty directory >>>>> mkdir /mnt/disk/dir1 @@ -678,8 +721,8 @@ POLICY UNLOCKED PROTECTORS #### Quiet version ```bash ->>>>> sudo fscrypt setup --quiet --force ->>>>> fscrypt setup /mnt/disk --quiet +>>>>> sudo fscrypt setup --quiet --force --all-users +>>>>> sudo fscrypt setup /mnt/disk --quiet --all-users >>>>> echo "hunter2" | fscrypt encrypt /mnt/disk/dir1 --quiet --source=custom_passphrase --name="Super Secret" ``` diff --git a/actions/context_test.go b/actions/context_test.go index 7b56d92..6e28857 100644 --- a/actions/context_test.go +++ b/actions/context_test.go @@ -27,6 +27,7 @@ import ( "testing" "time" + "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/util" "github.com/pkg/errors" ) @@ -67,7 +68,7 @@ func setupContext() (ctx *Context, err error) { return nil, err } - return ctx, ctx.Mount.Setup() + return ctx, ctx.Mount.Setup(filesystem.WorldWritable) } // Cleans up the testing config file and testing filesystem data. diff --git a/cli-tests/run.sh b/cli-tests/run.sh index dc17b5b..f6a4868 100755 --- a/cli-tests/run.sh +++ b/cli-tests/run.sh @@ -159,7 +159,7 @@ setup_for_test() # Give the tests their own fscrypt.conf. export FSCRYPT_CONF="$TMPDIR/fscrypt.conf" - fscrypt setup --time=1ms > /dev/null + fscrypt setup --time=1ms --quiet --all-users > /dev/null # The tests assume kernel support for v2 policies. if ! grep -q '"policy_version": "2"' "$FSCRYPT_CONF"; then @@ -171,7 +171,7 @@ EOF fi # Set up the test filesystems that aren't already set up. - fscrypt setup "$MNT" > /dev/null + fscrypt setup --quiet --all-users "$MNT" > /dev/null } run_test() diff --git a/cli-tests/t_encrypt.out b/cli-tests/t_encrypt.out index f067fc0..b92c9d9 100644 --- a/cli-tests/t_encrypt.out +++ b/cli-tests/t_encrypt.out @@ -1,7 +1,8 @@ # Try to encrypt a nonexistent directory [ERROR] fscrypt encrypt: no such file or directory -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted @@ -23,7 +24,8 @@ files into it, and securely delete the original directory. For example: Caution: due to the nature of modern storage devices and filesystems, the original data may still be recoverable from disk. It's much better to encrypt your files from the start. -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted @@ -45,13 +47,15 @@ files into it, and securely delete the original directory. For example: Caution: due to the nature of modern storage devices and filesystems, the original data may still be recoverable from disk. It's much better to encrypt your files from the start. -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted # Encrypt a directory as non-root user -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No custom protector "prot" @@ -67,7 +71,8 @@ Unlocked: Yes Protected with 1 protector: PROTECTOR LINKED DESCRIPTION desc1 No custom protector "prot" -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No custom protector "prot" @@ -94,7 +99,8 @@ desc1 No custom protector "prot" Encryption can only be enabled on a directory you own, even if you have write access to the directory. -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted diff --git a/cli-tests/t_encrypt_custom.out b/cli-tests/t_encrypt_custom.out index 8dd15e3..ac53d6f 100644 --- a/cli-tests/t_encrypt_custom.out +++ b/cli-tests/t_encrypt_custom.out @@ -1,6 +1,7 @@ # Encrypt with custom passphrase protector -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No custom protector "prot" @@ -28,7 +29,8 @@ Enter a name for the new protector: prot Enter custom passphrase for protector "prot": Confirm passphrase: "MNT/dir" is now encrypted, unlocked, and ready for use. -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc6 No custom protector "prot" @@ -49,7 +51,8 @@ desc6 No custom protector "prot" [ERROR] fscrypt encrypt: custom_passphrase protectors must be named Use --name=PROTECTOR_NAME to specify a protector name. -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted diff --git a/cli-tests/t_encrypt_login.out b/cli-tests/t_encrypt_login.out index 269f597..b84216a 100644 --- a/cli-tests/t_encrypt_login.out +++ b/cli-tests/t_encrypt_login.out @@ -7,7 +7,8 @@ IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for will lose access to this directory if you reinstall the operating system or move this filesystem to another system. -ext4 filesystem "MNT" has 2 protectors and 1 policy +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 Yes (MNT_ROOT) login protector for fscrypt-test-user @@ -15,7 +16,8 @@ desc2 No custom protector "Recovery passphrase POLICY UNLOCKED PROTECTORS desc3 Yes desc1, desc2 -ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No login protector for fscrypt-test-user @@ -57,7 +59,8 @@ IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for system or move this filesystem to another system. "MNT/dir" is now encrypted, unlocked, and ready for use. -ext4 filesystem "MNT" has 2 protectors and 1 policy +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc10 Yes (MNT_ROOT) login protector for fscrypt-test-user @@ -65,7 +68,8 @@ desc11 No custom protector "Recovery passphras POLICY UNLOCKED PROTECTORS desc12 Yes desc10, desc11 -ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc10 No login protector for fscrypt-test-user @@ -88,7 +92,8 @@ IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for will lose access to this directory if you reinstall the operating system or move this filesystem to another system. -ext4 filesystem "MNT" has 2 protectors and 1 policy +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc19 Yes (MNT_ROOT) login protector for fscrypt-test-user @@ -96,7 +101,8 @@ desc20 No custom protector "Recovery passphras POLICY UNLOCKED PROTECTORS desc21 Yes desc19, desc20 -ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc19 No login protector for fscrypt-test-user @@ -114,14 +120,16 @@ desc20 No custom protector "Recovery passphras Protector is owned by fscrypt-test-user:fscrypt-test-user # Encrypt with login protector with --no-recovery -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc28 Yes (MNT_ROOT) login protector for fscrypt-test-user POLICY UNLOCKED PROTECTORS desc29 Yes desc28 -ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc28 No login protector for fscrypt-test-user @@ -145,7 +153,8 @@ Unlocked: Yes Protected with 1 protector: PROTECTOR LINKED DESCRIPTION desc35 No login protector for fscrypt-test-user -ext4 filesystem "MNT_ROOT" has 1 protector and 1 policy +ext4 filesystem "MNT_ROOT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc35 No login protector for fscrypt-test-user @@ -159,18 +168,22 @@ desc34 Yes desc35 identified by user, not by name. To fix this, don't specify the --name=PROTECTOR_NAME option. -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. -ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies +ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted # Try to use the wrong login passphrase [ERROR] fscrypt encrypt: incorrect login passphrase -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. -ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies +ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted @@ -183,7 +196,8 @@ IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for will lose access to this directory if you reinstall the operating system or move this filesystem to another system. -ext4 filesystem "MNT" has 2 protectors and 1 policy +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc39 No custom protector "Recovery passphrase for dir" diff --git a/cli-tests/t_encrypt_raw_key.out b/cli-tests/t_encrypt_raw_key.out index 1f51dc0..4cfc050 100644 --- a/cli-tests/t_encrypt_raw_key.out +++ b/cli-tests/t_encrypt_raw_key.out @@ -1,6 +1,7 @@ # Encrypt with raw_key protector from file -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No raw key protector "prot" @@ -18,7 +19,8 @@ PROTECTOR LINKED DESCRIPTION desc1 No raw key protector "prot" # Encrypt with raw_key protector from stdin -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc6 No raw key protector "prot" @@ -37,21 +39,24 @@ desc6 No raw key protector "prot" # Try to encrypt with raw_key protector from file, using wrong key length [ERROR] fscrypt encrypt: TMPDIR/raw_key: key file must be 32 bytes -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted # Try to encrypt with raw_key protector from stdin, using wrong key length [ERROR] fscrypt encrypt: unexpected EOF -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. [ERROR] fscrypt status: file or directory "MNT/dir" is not encrypted # Encrypt with raw_key protector from file, unlock from stdin "MNT/dir" is now locked. -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc11 No raw key protector "prot" diff --git a/cli-tests/t_metadata.out b/cli-tests/t_metadata.out index fba816a..bbcc0f2 100644 --- a/cli-tests/t_metadata.out +++ b/cli-tests/t_metadata.out @@ -1,4 +1,5 @@ -ext4 filesystem "MNT" has 3 protectors and 1 policy +ext4 filesystem "MNT" has 3 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No custom protector "foo" @@ -7,7 +8,8 @@ desc3 No custom protector "baz" POLICY UNLOCKED PROTECTORS desc4 No desc1, desc2, desc3 -ext4 filesystem "MNT" has 2 protectors and 1 policy +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc1 No custom protector "foo" diff --git a/cli-tests/t_not_supported.sh b/cli-tests/t_not_supported.sh index 9ff90e1..8b52392 100755 --- a/cli-tests/t_not_supported.sh +++ b/cli-tests/t_not_supported.sh @@ -10,7 +10,7 @@ umount "$MNT" mount tmpfs -t tmpfs -o size=128m "$MNT" _print_header "Try to create fscrypt metadata on tmpfs" -_expect_failure "fscrypt setup '$MNT'" +_expect_failure "fscrypt setup --quiet '$MNT'" _print_header "Try to encrypt a directory on tmpfs" mkdir "$MNT/dir" diff --git a/cli-tests/t_setup.out b/cli-tests/t_setup.out index 943a781..6ea03e3 100644 --- a/cli-tests/t_setup.out +++ b/cli-tests/t_setup.out @@ -9,7 +9,8 @@ Skipping creating MNT_ROOT/.fscrypt because it already exists. Defaulting to policy_version 2 because kernel supports it. Customizing passphrase hashing difficulty for this system... Created global config file at "FSCRYPT_CONF". -Metadata directories created at "MNT_ROOT/.fscrypt". +Allow users other than root to create fscrypt metadata on this filesystem? (See +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] Metadata directories created at "MNT_ROOT/.fscrypt", writable by everyone. # fscrypt setup when fscrypt.conf already exists (cancel) Replace "FSCRYPT_CONF"? [y/N] [ERROR] fscrypt setup: operation canceled @@ -31,7 +32,8 @@ If desired, use --force to automatically run destructive operations. # fscrypt setup --quiet --force when fscrypt.conf already exists # fscrypt setup filesystem -Metadata directories created at "MNT/.fscrypt". +Allow users other than root to create fscrypt metadata on this filesystem? (See +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] Metadata directories created at "MNT/.fscrypt", writable by everyone. # fscrypt setup filesystem (already set up) [ERROR] fscrypt setup: filesystem MNT is already setup for diff --git a/cli-tests/t_setup.sh b/cli-tests/t_setup.sh index a8a62a3..f7e302d 100755 --- a/cli-tests/t_setup.sh +++ b/cli-tests/t_setup.sh @@ -14,7 +14,7 @@ fscrypt setup --time=1ms _print_header "fscrypt setup creates fscrypt.conf and /.fscrypt" _rm_metadata "$MNT_ROOT" rm -f "$FSCRYPT_CONF" -fscrypt setup --time=1ms +echo y | fscrypt setup --time=1ms [ -e "$MNT_ROOT/.fscrypt" ] _print_header "fscrypt setup when fscrypt.conf already exists (cancel)" @@ -37,7 +37,7 @@ fscrypt setup --quiet --force --time=1ms _print_header "fscrypt setup filesystem" _rm_metadata "$MNT" -fscrypt setup "$MNT" +echo y | fscrypt setup "$MNT" [ -e "$MNT/.fscrypt" ] _print_header "fscrypt setup filesystem (already set up)" diff --git a/cli-tests/t_single_user.out b/cli-tests/t_single_user.out new file mode 100644 index 0000000..e788b3e --- /dev/null +++ b/cli-tests/t_single_user.out @@ -0,0 +1,30 @@ +ext4 filesystem "MNT" has 0 protectors and 0 policies. +Only root can create fscrypt metadata on this filesystem. + +ext4 filesystem "MNT" has 0 protectors and 0 policies. +Only root can create fscrypt metadata on this filesystem. + + +# Encrypt, lock, and unlock as root +"MNT/dir" is now locked. + +# Encrypt as root with user's login protector + +IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for + important recovery instructions. It is *strongly recommended* to + record the recovery passphrase in a secure location; otherwise you + will lose access to this directory if you reinstall the operating + system or move this filesystem to another system. + +Protector desc1 no longer protecting policy desc2. +"MNT/dir" is now locked. +Enter login passphrase for fscrypt-test-user: "MNT/dir" is now unlocked and ready for use. + +# Encrypt as user (should fail) +[ERROR] fscrypt encrypt: user lacks permission to create fscrypt metadata on + MNT + +For how to allow users to create fscrypt metadata on a filesystem, refer to +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem + +# Encrypt as user if they set up filesystem (should succeed) diff --git a/cli-tests/t_single_user.sh b/cli-tests/t_single_user.sh new file mode 100755 index 0000000..c569f20 --- /dev/null +++ b/cli-tests/t_single_user.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Test 'fscrypt setup' without --all-users. + +cd "$(dirname "$0")" +. common.sh + +_rm_metadata "$MNT_ROOT" +_rm_metadata "$MNT" +rm "$FSCRYPT_CONF" +fscrypt setup --time=1ms --quiet +fscrypt setup --time=1ms --quiet "$MNT" +fscrypt status "$MNT" +_user_do "fscrypt status \"$MNT\"" + +dir=$MNT/dir + +begin() +{ + _reset_filesystems + mkdir "$dir" + _print_header "$1" +} + +begin "Encrypt, lock, and unlock as root" +echo hunter2 | fscrypt encrypt --quiet --name=dir --skip-unlock "$dir" +echo hunter2 | fscrypt unlock --quiet "$dir" +fscrypt lock "$dir" + +begin "Encrypt as root with user's login protector" +echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --user="$TEST_USER" "$dir" +# The user should be able to update the policy and protectors created by the +# above command themselves. The easiest way to test this is by updating the +# policy to remove the auto-generated recovery protector. This verifies that +# (a) the policy was made owned by the user, and that (b) policy updates fall +# back to overwrites when the process cannot write to the containing directory. +# (It would be better to test updating the protectors too, but this is the +# easiest test to do here.) +policy=$(fscrypt status "$dir" | awk '/Policy/{print $2}') +recovery_protector=$(_get_protector_descriptor "$MNT" custom 'Recovery passphrase for dir') +_user_do "fscrypt metadata remove-protector-from-policy --force --protector=$MNT:$recovery_protector --policy=$MNT:$policy" +chown "$TEST_USER" "$dir" +_user_do "fscrypt lock $dir" +_user_do "echo TEST_USER_PASS | fscrypt unlock $dir" + +begin "Encrypt as user (should fail)" +chown "$TEST_USER" "$dir" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=dir --skip-unlock \"$dir\"" + +begin "Encrypt as user if they set up filesystem (should succeed)" +_rm_metadata "$MNT" +chown "$TEST_USER" "$MNT" +chown "$TEST_USER" "$dir" +_user_do "fscrypt setup --time=1ms --quiet $MNT" +_user_do "echo hunter2 | fscrypt encrypt --quiet --name=dir3 --skip-unlock \"$dir\"" diff --git a/cli-tests/t_status.out b/cli-tests/t_status.out index 0d478b5..eb425d0 100644 --- a/cli-tests/t_status.out +++ b/cli-tests/t_status.out @@ -4,9 +4,11 @@ ext4 supported Yes ext4 supported Yes # Get status of setup mountpoint -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. -ext4 filesystem "MNT" has 0 protectors and 0 policies +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. # Get status of unencrypted directory on setup mountpoint diff --git a/cli-tests/t_v1_policy.out b/cli-tests/t_v1_policy.out index 9adb00a..1f4f9d7 100644 --- a/cli-tests/t_v1_policy.out +++ b/cli-tests/t_v1_policy.out @@ -120,7 +120,8 @@ Unlocked: Partially (incompletely locked, or unlocked by another user) Protected with 1 protector: PROTECTOR LINKED DESCRIPTION desc2 No custom protector "prot" -ext4 filesystem "MNT" has 1 protector and 1 policy +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. PROTECTOR LINKED DESCRIPTION desc2 No custom protector "prot" diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 023c0fa..30aa3a7 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -63,7 +63,7 @@ var Setup = cli.Command{ the README). This may require root privileges.`, mountpointArg, actions.ConfigFileLocation, shortDisplay(timeTargetFlag)), - Flags: []cli.Flag{timeTargetFlag, forceFlag}, + Flags: []cli.Flag{timeTargetFlag, forceFlag, allUsersSetupFlag}, Action: setupAction, } @@ -468,7 +468,7 @@ var Lock = cli.Command{ recoverable by an attacker who compromises system memory. To be fully safe, you must reboot with a power cycle.`, directoryArg, shortDisplay(dropCachesFlag)), - Flags: []cli.Flag{dropCachesFlag, userFlag, allUsersFlag}, + Flags: []cli.Flag{dropCachesFlag, userFlag, allUsersLockFlag}, Action: lockAction, } @@ -502,7 +502,7 @@ func lockAction(c *cli.Context) error { return newExitError(c, ErrDropCachesPerm) } - if err = policy.Deprovision(allUsersFlag.Value); err != nil { + if err = policy.Deprovision(allUsersLockFlag.Value); err != nil { switch err { case keyring.ErrKeyNotPresent: break diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index bcf5b59..2d76792 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -232,6 +232,10 @@ func getErrorSuggestions(err error) string { } } return "" + case *filesystem.ErrNoCreatePermission: + return `For how to allow users to create fscrypt metadata on a + filesystem, refer to + https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem` case *filesystem.ErrNotSetup: return fmt.Sprintf(`Run "sudo fscrypt setup %s" to use fscrypt on this filesystem.`, e.Mount.Path) diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index 044b71e..1b41839 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -116,7 +116,8 @@ var ( allFlags = []prettyFlag{helpFlag, versionFlag, verboseFlag, quietFlag, forceFlag, skipUnlockFlag, timeTargetFlag, sourceFlag, nameFlag, keyFileFlag, protectorFlag, - unlockWithFlag, policyFlag, allUsersFlag, noRecoveryFlag} + unlockWithFlag, policyFlag, allUsersLockFlag, allUsersSetupFlag, + noRecoveryFlag} // universalFlags contains flags that should be on every command universalFlags = []cli.Flag{verboseFlag, quietFlag, helpFlag} ) @@ -164,7 +165,7 @@ var ( privileges.`, Default: true, } - allUsersFlag = &boolFlag{ + allUsersLockFlag = &boolFlag{ Name: "all-users", Usage: `Lock the directory no matter which user(s) have unlocked it. Requires root privileges. This flag is only @@ -172,6 +173,15 @@ var ( different from the one you're locking it as. This flag is only implemented for v2 encryption policies.`, } + allUsersSetupFlag = &boolFlag{ + Name: "all-users", + Usage: `When setting up a filesystem for fscrypt, allow users + other than the calling user (typically root) to create + fscrypt policies and protectors on the filesystem. Note + that this will create a world-writable directory, which + users could use to fill up the entire filesystem. Hence, + this option may not be appropriate for some systems.`, + } noRecoveryFlag = &boolFlag{ Name: "no-recovery", Usage: `Don't generate a recovery passphrase.`, diff --git a/cmd/fscrypt/setup.go b/cmd/fscrypt/setup.go index 7b9bebb..1da0d16 100644 --- a/cmd/fscrypt/setup.go +++ b/cmd/fscrypt/setup.go @@ -26,6 +26,7 @@ import ( "os" "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/util" ) @@ -80,11 +81,47 @@ func setupFilesystem(w io.Writer, path string) error { if err != nil { return err } + username := ctx.TargetUser.Username - if err = ctx.Mount.Setup(); err != nil { + err = ctx.Mount.CheckSetup() + if err == nil { + return &filesystem.ErrAlreadySetup{Mount: ctx.Mount} + } + if _, ok := err.(*filesystem.ErrNotSetup); !ok { return err } - fmt.Fprintf(w, "Metadata directories created at %q.\n", ctx.Mount.BaseDir()) + allUsers := allUsersSetupFlag.Value + if !allUsers { + thisFilesystem := "this filesystem" + if ctx.Mount.Path == "/" { + thisFilesystem = "the root filesystem" + } + prompt := fmt.Sprintf(`Allow users other than %s to create +fscrypt metadata on %s? (See +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem)`, + username, thisFilesystem) + allUsers, err = askQuestion(wrapText(prompt, 0), false) + if err != nil { + return err + } + } + var setupMode filesystem.SetupMode + if allUsers { + setupMode = filesystem.WorldWritable + } else { + setupMode = filesystem.SingleUserWritable + } + if err = ctx.Mount.Setup(setupMode); err != nil { + return err + } + + if allUsers { + fmt.Fprintf(w, "Metadata directories created at %q, writable by everyone.\n", + ctx.Mount.BaseDir()) + } else { + fmt.Fprintf(w, "Metadata directories created at %q, writable by %s only.\n", + ctx.Mount.BaseDir(), username) + } return nil } diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go index d10dfd8..54c6f1f 100644 --- a/cmd/fscrypt/status.go +++ b/cmd/fscrypt/status.go @@ -165,9 +165,18 @@ func writeFilesystemStatus(w io.Writer, ctx *actions.Context) error { return err } - fmt.Fprintf(w, "%s filesystem %q has %s and %s\n\n", ctx.Mount.FilesystemType, + fmt.Fprintf(w, "%s filesystem %q has %s and %s.\n", ctx.Mount.FilesystemType, ctx.Mount.Path, pluralize(len(options), "protector"), pluralize(len(policyDescriptors), "policy")) + if setupMode, user, err := ctx.Mount.GetSetupMode(); err == nil { + switch setupMode { + case filesystem.WorldWritable: + fmt.Fprintf(w, "All users can create fscrypt metadata on this filesystem.\n") + case filesystem.SingleUserWritable: + fmt.Fprintf(w, "Only %s can create fscrypt metadata on this filesystem.\n", user.Username) + } + } + fmt.Fprintf(w, "\n") if len(options) > 0 { writeOptions(w, options) diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index c39514a..1450f0f 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -96,6 +96,16 @@ func (err *ErrMakeLink) Error() string { err.Target.Path, err.UnderlyingError) } +// ErrNoCreatePermission indicates that the current user lacks permission to +// create fscrypt metadata on the given filesystem. +type ErrNoCreatePermission struct { + Mount *Mount +} + +func (err *ErrNoCreatePermission) Error() string { + return fmt.Sprintf("user lacks permission to create fscrypt metadata on %s", err.Mount.Path) +} + // ErrNotAMountpoint indicates that a path is not a mountpoint. type ErrNotAMountpoint struct { Path string @@ -209,9 +219,6 @@ const ( // The base directory should be read-only (except for the creator) basePermissions = 0755 - // The subdirectories should be writable to everyone, but they have the - // sticky bit set so users cannot delete other users' metadata. - dirPermissions = os.ModeSticky | 0777 // The metadata files are globally visible, but can only be deleted by // the user that created them filePermissions = 0644 @@ -223,6 +230,18 @@ const ( maxMetadataFileSize = 16384 ) +// SetupMode is a mode for creating the fscrypt metadata directories. +type SetupMode int + +const ( + // SingleUserWritable specifies to make the fscrypt metadata directories + // writable by a single user (usually root) only. + SingleUserWritable SetupMode = iota + // WorldWritable specifies to make the fscrypt metadata directories + // world-writable (with the sticky bit set). + WorldWritable +) + func (m *Mount) String() string { return fmt.Sprintf(`%s FilesystemType: %s @@ -359,8 +378,8 @@ func (m *Mount) CheckSetup() error { } // Run all the checks so we will always get all the warnings baseGood := isDirCheckPerm(m.BaseDir(), basePermissions) - policyGood := isDirCheckPerm(m.PolicyDir(), dirPermissions) - protectorGood := isDirCheckPerm(m.ProtectorDir(), dirPermissions) + policyGood := isDir(m.PolicyDir()) + protectorGood := isDir(m.ProtectorDir()) if baseGood && policyGood && protectorGood { return nil @@ -370,7 +389,7 @@ func (m *Mount) CheckSetup() error { // makeDirectories creates the three metadata directories with the correct // permissions. Note that this function overrides the umask. -func (m *Mount) makeDirectories() error { +func (m *Mount) makeDirectories(setupMode SetupMode) error { // Zero the umask so we get the permissions we want oldMask := unix.Umask(0) defer func() { @@ -380,17 +399,51 @@ func (m *Mount) makeDirectories() error { if err := os.Mkdir(m.BaseDir(), basePermissions); err != nil { return err } - if err := os.Mkdir(m.PolicyDir(), dirPermissions); err != nil { + + var dirMode os.FileMode + switch setupMode { + case SingleUserWritable: + dirMode = 0755 + case WorldWritable: + dirMode = os.ModeSticky | 0777 + } + if err := os.Mkdir(m.PolicyDir(), dirMode); err != nil { return err } - return os.Mkdir(m.ProtectorDir(), dirPermissions) + return os.Mkdir(m.ProtectorDir(), dirMode) +} + +// GetSetupMode returns the current mode for fscrypt metadata creation on this +// filesystem. +func (m *Mount) GetSetupMode() (SetupMode, *user.User, error) { + info1, err1 := os.Stat(m.PolicyDir()) + info2, err2 := os.Stat(m.ProtectorDir()) + + if err1 == nil && err2 == nil { + mask := os.ModeSticky | 0777 + mode1 := info1.Mode() & mask + mode2 := info2.Mode() & mask + uid1 := info1.Sys().(*syscall.Stat_t).Uid + uid2 := info2.Sys().(*syscall.Stat_t).Uid + user, err := util.UserFromUID(int64(uid1)) + if err == nil && mode1 == mode2 && uid1 == uid2 { + switch mode1 { + case mask: + return WorldWritable, nil, nil + case 0755: + return SingleUserWritable, user, nil + } + } + log.Printf("filesystem %s uses custom permissions on metadata directories", m.Path) + } + return -1, nil, errors.New("unable to determine setup mode") } // Setup sets up the filesystem for use with fscrypt. Note that this merely // creates the appropriate files on the filesystem. It does not actually modify // the filesystem's feature flags. This operation is atomic; it either succeeds // or no files in the baseDir are created. -func (m *Mount) Setup() error { +func (m *Mount) Setup(mode SetupMode) error { if m.CheckSetup() == nil { return &ErrAlreadySetup{m} } @@ -404,7 +457,7 @@ func (m *Mount) Setup() error { } defer os.RemoveAll(temp.Path) - if err = temp.makeDirectories(); err != nil { + if err = temp.makeDirectories(mode); err != nil { return err } @@ -484,6 +537,7 @@ func (m *Mount) writeData(path string, data []byte, owner *user.User) error { log.Printf("trying non-atomic overwrite of %q", path) return m.overwriteDataNonAtomic(path, data) } + return &ErrNoCreatePermission{m} } return err } diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go index 7aa97cb..365c5cb 100644 --- a/filesystem/filesystem_test.go +++ b/filesystem/filesystem_test.go @@ -92,7 +92,7 @@ func getSetupMount(t *testing.T) (*Mount, error) { if err != nil { return nil, err } - return mnt, mnt.Setup() + return mnt, mnt.Setup(WorldWritable) } // Tests that the setup works and creates the correct files @@ -153,7 +153,7 @@ func testSetupWithSymlink(t *testing.T, mnt *Mount, symlinkTarget string, realDi } defer os.Remove(rawBaseDir) - if err := mnt.Setup(); err != nil { + if err := mnt.Setup(WorldWritable); err != nil { t.Fatal(err) } defer mnt.RemoveAllMetadata() @@ -203,6 +203,35 @@ func TestSetupWithRelativeSymlink(t *testing.T) { testSetupWithSymlink(t, mnt, ".fscrypt-real", realDir) } +func testSetupMode(t *testing.T, mnt *Mount, setupMode SetupMode, expectedPerms os.FileMode) { + mnt.RemoveAllMetadata() + if err := mnt.Setup(setupMode); err != nil { + t.Fatal(err) + } + dirNames := []string{"policies", "protectors"} + for _, dirName := range dirNames { + fi, err := os.Stat(filepath.Join(mnt.Path, ".fscrypt", dirName)) + if err != nil { + t.Fatal(err) + } + if fi.Mode()&(os.ModeSticky|0777) != expectedPerms { + t.Errorf("directory %s doesn't have permissions %o", dirName, expectedPerms) + } + } +} + +// Tests that the supported setup modes (WorldWritable and SingleUserWritable) +// work as intended. +func TestSetupModes(t *testing.T) { + mnt, err := getTestMount(t) + if err != nil { + t.Fatal(err) + } + defer mnt.RemoveAllMetadata() + testSetupMode(t, mnt, WorldWritable, os.ModeSticky|0777) + testSetupMode(t, mnt, SingleUserWritable, 0755) +} + // Adding a good Protector should succeed, adding a bad one should fail func TestAddProtector(t *testing.T) { mnt, err := getSetupMount(t) @@ -384,7 +413,7 @@ func getTwoSetupMounts(t *testing.T) (realMnt, fakeMnt *Mount, err error) { return } fakeMnt = &Mount{Path: fakeMountpoint, FilesystemType: realMnt.FilesystemType} - err = fakeMnt.Setup() + err = fakeMnt.Setup(WorldWritable) return } -- cgit v1.2.3