From ae886a89f541a74255c9a41f7fa504a82ee6413e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Mar 2020 21:10:58 -0700 Subject: Simplify choosing the key description prefix There's no real need to allow users to choose the key description prefix (a.k.a. the "service"), since on ext4 and f2fs we can just use "ext4" and "f2fs" for compatibility with all kernels both old and new, and on other filesystems we can just use "fscrypt". So, let's do that. Since this removes the point of the "--legacy" option to 'fscrypt setup' and the "compatibility" field in /etc/fscrypt.conf, remove those too. Specifically, we start ignoring the "compatibility" in existing config files and not writing it to new ones. The corresponding protobuf field number and name are reserved. We stop accepting the "--legacy" option at all, although since it was default true and there was no real reason for anyone to change it to false, probably no one will notice. If anyone does, they should just stop specifying the option. Note that this change only affects user keyrings and thus only affects v1 encryption policies, which are deprecated in favor of v2 anyway. --- keyring/keyring_test.go | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) (limited to 'keyring/keyring_test.go') diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go index 8912556..2208105 100644 --- a/keyring/keyring_test.go +++ b/keyring/keyring_test.go @@ -23,8 +23,6 @@ import ( "strconv" "testing" - "golang.org/x/sys/unix" - "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" @@ -47,7 +45,6 @@ func makeKey(b byte, n int) (*crypto.Key, error) { } var ( - defaultService = unix.FSCRYPT_KEY_DESC_PREFIX testUser, _ = util.EffectiveUser() fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) @@ -166,28 +163,11 @@ func testAddAndRemoveKey(t *testing.T, descriptor string, options *Options) { assertKeyStatus(t, descriptor, options, KeyAbsent) } -func TestUserKeyringDefaultService(t *testing.T) { - options := &Options{ - User: testUser, - Service: defaultService, - UseFsKeyringForV1Policies: false, - } - testAddAndRemoveKey(t, fakeV1Descriptor, options) -} - -func TestUserKeyringExt4Service(t *testing.T) { - options := &Options{ - User: testUser, - Service: "ext4:", - UseFsKeyringForV1Policies: false, - } - testAddAndRemoveKey(t, fakeV1Descriptor, options) -} - -func TestUserKeyringF2fsService(t *testing.T) { +func TestUserKeyring(t *testing.T) { + mount := getTestMount(t) options := &Options{ + Mount: mount, User: testUser, - Service: "f2fs:", UseFsKeyringForV1Policies: false, } testAddAndRemoveKey(t, fakeV1Descriptor, options) -- cgit v1.3 From 8d71383bc08478313c221c8ab20e8902de1bb28b Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Mar 2020 21:10:58 -0700 Subject: Improve error message when unlocking v2 policy is unsupported If trying to unlock a v2-encrypted directory fails because the kernel lacks support for v2 policies, show a better error message. This can happen if someone downgrades their kernel or tries to access encrypted directories on removable storage from a computer with an older kernel. Detecting this case is difficult since all we have to go with is EACCES when opening the directory. Implement a heuristic where if get EACCES, we actually have read access to the directory, and the kernel doesn't support v2 policies, we show the improved error message. Before: # fscrypt unlock dir [ERROR] fscrypt unlock: open dir: permission denied After: # fscrypt unlock dir [ERROR] fscrypt unlock: open dir: permission denied This may be caused by the directory using a v2 encryption policy and the current kernel not supporting it. If indeed the case, then this directory can only be used on kernel v5.4 and later. You can create directories accessible on older kernels by changing policy_version to 1 in /etc/fscrypt.conf. --- actions/policy.go | 11 +++++++++++ cmd/fscrypt/errors.go | 7 +++++++ filesystem/path.go | 6 ++++++ filesystem/path_test.go | 27 +++++++++++++++++++++++++++ keyring/fs_keyring.go | 4 ++-- keyring/keyring.go | 4 ++-- keyring/keyring_test.go | 2 +- 7 files changed, 56 insertions(+), 5 deletions(-) (limited to 'keyring/keyring_test.go') diff --git a/actions/policy.go b/actions/policy.go index b7fe5a6..3baad72 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -22,6 +22,7 @@ package actions import ( "fmt" "log" + "os" "github.com/golang/protobuf/proto" "github.com/pkg/errors" @@ -41,6 +42,7 @@ var ( ErrOnlyProtector = errors.New("cannot remove the only protector for a policy") ErrAlreadyProtected = errors.New("policy already protected by protector") ErrNotProtected = errors.New("policy not protected by protector") + ErrAccessDeniedPossiblyV2 = errors.New("permission denied") ) // PurgeAllPolicies removes all policy keys on the filesystem from the kernel @@ -152,6 +154,15 @@ func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) { // the path, and the data we get from the mountpoint. pathData, err := metadata.GetPolicy(path) if err != nil { + // On kernels that don't support v2 encryption policies, trying + // to open a directory with a v2 policy simply gave EACCES. This + // is ambiguous with other errors, but try to detect this case + // and show a better error message. + if os.IsPermission(err) && + filesystem.HaveReadAccessTo(path) && + !keyring.IsFsKeyringSupported(ctx.Mount) { + return nil, errors.Wrapf(ErrAccessDeniedPossiblyV2, "open %s", path) + } return nil, err } descriptor := pathData.KeyDescriptor diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index c242552..8bda921 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -131,6 +131,13 @@ func getErrorSuggestions(err error) string { metadata is corrupted.` case actions.ErrMissingProtectorName: return fmt.Sprintf("Use %s to specify a protector name.", shortDisplay(nameFlag)) + case actions.ErrAccessDeniedPossiblyV2: + return fmt.Sprintf(`This may be caused by the directory using a v2 + encryption policy and the current kernel not supporting it. If + indeed the case, then this directory can only be used on kernel + v5.4 and later. You can create directories accessible on older + kernels by changing policy_version to 1 in %s.`, + actions.ConfigFileLocation) case ErrNoDestructiveOps: return fmt.Sprintf("Use %s to automatically run destructive operations.", shortDisplay(forceFlag)) case ErrSpecifyProtector: diff --git a/filesystem/path.go b/filesystem/path.go index b9b403d..274dc0a 100644 --- a/filesystem/path.go +++ b/filesystem/path.go @@ -78,6 +78,12 @@ func isRegularFile(path string) bool { return err == nil && info.Mode().IsRegular() } +// HaveReadAccessTo returns true if the process has read access to a file or +// directory, without actually opening it. +func HaveReadAccessTo(path string) bool { + return unix.Access(path, unix.R_OK) == nil +} + // DeviceNumber represents a combined major:minor device number. type DeviceNumber uint64 diff --git a/filesystem/path_test.go b/filesystem/path_test.go index eef5ce3..4152037 100644 --- a/filesystem/path_test.go +++ b/filesystem/path_test.go @@ -20,6 +20,8 @@ package filesystem import ( "fmt" + "io/ioutil" + "os" "testing" ) @@ -52,3 +54,28 @@ func TestDeviceNumber(t *testing.T) { t.Error("Should have failed to parse invalid device number") } } + +func TestHaveReadAccessTo(t *testing.T) { + file, err := ioutil.TempFile("", "fscrypt_test") + if err != nil { + t.Fatal(err) + } + file.Close() + defer os.Remove(file.Name()) + + testCases := map[os.FileMode]bool{ + 0444: true, + 0400: true, + 0000: false, + 0040: false, // user bits take priority in Linux + 0004: false, // user bits take priority in Linux + } + for mode, readable := range testCases { + if err := os.Chmod(file.Name(), mode); err != nil { + t.Error(err) + } + if HaveReadAccessTo(file.Name()) != readable { + t.Errorf("Expected readable=%v on mode=0%03o", readable, mode) + } + } +} diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go index 42c1648..f0016a4 100644 --- a/keyring/fs_keyring.go +++ b/keyring/fs_keyring.go @@ -79,10 +79,10 @@ func checkForFsKeyringSupport(mount *filesystem.Mount) bool { return true } -// isFsKeyringSupported returns true if the kernel supports the ioctls to +// IsFsKeyringSupported returns true if the kernel supports the ioctls to // add/remove fscrypt keys directly to/from the filesystem. For support to be // detected, the given Mount must be for a filesystem that supports fscrypt. -func isFsKeyringSupported(mount *filesystem.Mount) bool { +func IsFsKeyringSupported(mount *filesystem.Mount) bool { fsKeyringSupportedLock.Lock() defer fsKeyringSupportedLock.Unlock() if !fsKeyringSupportedKnown { diff --git a/keyring/keyring.go b/keyring/keyring.go index e232de3..6623943 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -75,11 +75,11 @@ func shouldUseFsKeyring(descriptor string, options *Options) (bool, error) { // use_fs_keyring_for_v1_policies is set in /etc/fscrypt.conf and the // kernel supports it. if len(descriptor) == hex.EncodedLen(unix.FSCRYPT_KEY_DESCRIPTOR_SIZE) { - return options.UseFsKeyringForV1Policies && isFsKeyringSupported(options.Mount), nil + return options.UseFsKeyringForV1Policies && IsFsKeyringSupported(options.Mount), nil } // For v2 encryption policy keys, always use the filesystem keyring; the // kernel doesn't support any other way. - if !isFsKeyringSupported(options.Mount) { + if !IsFsKeyringSupported(options.Mount) { return true, ErrV2PoliciesUnsupported } return true, nil diff --git a/keyring/keyring_test.go b/keyring/keyring_test.go index 2208105..26f6036 100644 --- a/keyring/keyring_test.go +++ b/keyring/keyring_test.go @@ -81,7 +81,7 @@ func getTestMount(t *testing.T) *filesystem.Mount { // filesystem keyring and v2 encryption policies are supported. func getTestMountV2(t *testing.T) *filesystem.Mount { mount := getTestMount(t) - if !isFsKeyringSupported(mount) { + if !IsFsKeyringSupported(mount) { t.Skip("No support for fs keyring, skipping test.") } return mount -- cgit v1.3