]> git.apps.os.sepia.ceph.com Git - fscrypt.git/commitdiff
Make 'fscrypt setup' offer a choice of directory modes
authorEric Biggers <ebiggers@google.com>
Wed, 23 Feb 2022 20:35:04 +0000 (12:35 -0800)
committerEric Biggers <ebiggers@google.com>
Wed, 23 Feb 2022 20:35:04 +0000 (12:35 -0800)
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.

22 files changed:
README.md
actions/context_test.go
cli-tests/run.sh
cli-tests/t_encrypt.out
cli-tests/t_encrypt_custom.out
cli-tests/t_encrypt_login.out
cli-tests/t_encrypt_raw_key.out
cli-tests/t_metadata.out
cli-tests/t_not_supported.sh
cli-tests/t_setup.out
cli-tests/t_setup.sh
cli-tests/t_single_user.out [new file with mode: 0644]
cli-tests/t_single_user.sh [new file with mode: 0755]
cli-tests/t_status.out
cli-tests/t_v1_policy.out
cmd/fscrypt/commands.go
cmd/fscrypt/errors.go
cmd/fscrypt/flags.go
cmd/fscrypt/setup.go
cmd/fscrypt/status.go
filesystem/filesystem.go
filesystem/filesystem_test.go

index 75b3d621827aa2d730755743bdd2be5bad14ee4c..26fd08406bbf3f6322b45b3c0e6ef4a910d12072 100644 (file)
--- 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"
 ```
 
index 7b56d92c85a322eba63dbaa11555e1c744fbaced..6e28857619b445e45d06bde76dca0c7139e9b2f7 100644 (file)
@@ -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.
index dc17b5b923c4634f23e940180dd98af733ba015d..f6a4868218f4f69633a969718d83cafd423b12ca 100755 (executable)
@@ -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()
index f067fc0e9614b82ca7fba22efbeab910a1a363de..b92c9d98e0dab2f3a80d88bad130493f45f5b939 100644 (file)
@@ -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
index 8dd15e3a516949718b0f6ea2fc2555ed3e086aa4..ac53d6fd1c43f6391b75b648ce0c51bc1a2a4cf4 100644 (file)
@@ -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": \r
 Confirm passphrase: \r
 "MNT/dir" is now encrypted, unlocked, and ready for use.\r
-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
index 269f597847dfb969a7eb4549e2b8c82e0ff220eb..b84216a1eb1bfd2d6239ff8afbf38d0df97edc1e 100644 (file)
@@ -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.\r
 \r
 "MNT/dir" is now encrypted, unlocked, and ready for use.\r
-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"
index 1f51dc0d6940ae47d8145b9dff9489fbc91eb0ea..4cfc05007e299857029545389724fefb265d2b97 100644 (file)
@@ -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"
index fba816afc501a9a2e926795cb8da0e4ef7130ad6..bbcc0f29eed6e7b2912b0d4b3a1e961ca99c7e00 100644 (file)
@@ -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"
index 9ff90e17aff286bf91d8f0d2a5426f8eaf05db71..8b52392d2eeed0d7a598b2ec07b40306f4d7d13d 100755 (executable)
@@ -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"
index 943a7813de38fc7de9ff924f01c00d033f27c85f..6ea03e3f10d2df7c0cff227db2fa409e233edeb6 100644 (file)
@@ -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
index a8a62a37ed438d7bbb9574bae7508fde52927202..f7e302d8480e14fa912d501437ad52891d8e9d60 100755 (executable)
@@ -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 (file)
index 0000000..e788b3e
--- /dev/null
@@ -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 (executable)
index 0000000..c569f20
--- /dev/null
@@ -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\""
index 0d478b5aca39a127df76a6e4f7550608afdd6cf0..eb425d0e05d280b7dd12ac44f048af63ed4c234d 100644 (file)
@@ -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
index 9adb00aec796ad54b9357e9e5570bc377743e4e0..1f4f9d77a909ea89b33cca0f6e78933c21e6ef6b 100644 (file)
@@ -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"
index 023c0fa2d4eab9d40dcbd638d769d0245abcf578..30aa3a7ae35d70926656f671f3e75e9b04da0ea2 100644 (file)
@@ -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
index bcf5b595668d65aa92d8712dced51ed567267e87..2d76792d8ae7cedf8e563e0f09fefe79b7413b94 100644 (file)
@@ -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)
index 044b71e8cee0601286eb0bed425f7382d476e477..1b41839d5503f7ff16c8269f79cfe4c46374c25e 100644 (file)
@@ -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.`,
index 7b9bebbb2ff773518d2567f3547e39e2f203a533..1da0d1631835b5d1c13ce574b305e2f60cf307ea 100644 (file)
@@ -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
 }
index d10dfd8cd5f4ff9de9bee7aae112bc6cd536989e..54c6f1f3d1a77101bba74f9e3838a226dea920e0 100644 (file)
@@ -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)
index c39514ac5730cea78ced697bdb98d54e98cb1404..1450f0f0d11cbca9e5a5bcd670426bbee7cd102d 100644 (file)
@@ -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
        }
index 7aa97cb45db6012956da05bfb01afe92186fa48e..365c5cbc455de039c69dae7ff387509e72247301 100644 (file)
@@ -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
 }