aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2022-02-23 12:35:04 -0800
committerEric Biggers <ebiggers@google.com>2022-02-23 12:35:04 -0800
commit6e355131670ad014e45f879475ddf800f0080d41 (patch)
treeb323dd02a668b36a4b07f9f8275d6555acb00dbb
parent45599bdfad300f1a034c70dd70b4bd180d66f52c (diff)
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.
-rw-r--r--README.md53
-rw-r--r--actions/context_test.go3
-rwxr-xr-xcli-tests/run.sh4
-rw-r--r--cli-tests/t_encrypt.out18
-rw-r--r--cli-tests/t_encrypt_custom.out9
-rw-r--r--cli-tests/t_encrypt_login.out42
-rw-r--r--cli-tests/t_encrypt_raw_key.out15
-rw-r--r--cli-tests/t_metadata.out6
-rwxr-xr-xcli-tests/t_not_supported.sh2
-rw-r--r--cli-tests/t_setup.out6
-rwxr-xr-xcli-tests/t_setup.sh4
-rw-r--r--cli-tests/t_single_user.out30
-rwxr-xr-xcli-tests/t_single_user.sh55
-rw-r--r--cli-tests/t_status.out6
-rw-r--r--cli-tests/t_v1_policy.out3
-rw-r--r--cmd/fscrypt/commands.go6
-rw-r--r--cmd/fscrypt/errors.go4
-rw-r--r--cmd/fscrypt/flags.go14
-rw-r--r--cmd/fscrypt/setup.go41
-rw-r--r--cmd/fscrypt/status.go11
-rw-r--r--filesystem/filesystem.go74
-rw-r--r--filesystem/filesystem_test.go35
22 files changed, 374 insertions, 67 deletions
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
}