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.go | 22 ++++++++++++++++------ keyring/keyring_test.go | 26 +++----------------------- 2 files changed, 19 insertions(+), 29 deletions(-) (limited to 'keyring') diff --git a/keyring/keyring.go b/keyring/keyring.go index 5a75153..f873bac 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -62,9 +62,6 @@ type Options struct { Mount *filesystem.Mount // User is the user for whom the key should be added/removed/gotten. User *user.User - // Service is the prefix to prepend to the description of the keys in - // user keyrings. Not relevant for filesystem keyrings. - Service string // UseFsKeyringForV1Policies is true if keys for v1 encryption policies // should be put in the filesystem's keyring (if supported) rather than // in the user's keyring. Note that this makes AddEncryptionKey and @@ -84,6 +81,19 @@ func shouldUseFsKeyring(descriptor string, options *Options) bool { return true } +// buildKeyDescription builds the description for an fscrypt key of type +// "logon". For ext4 and f2fs, it uses the legacy filesystem-specific prefixes +// for compatibility with kernels before v4.8 and v4.6 respectively. For other +// filesystems it uses the generic prefix "fscrypt". +func buildKeyDescription(options *Options, descriptor string) string { + switch options.Mount.FilesystemType { + case "ext4", "f2fs": + return options.Mount.FilesystemType + ":" + descriptor + default: + return unix.FSCRYPT_KEY_DESC_PREFIX + descriptor + } +} + // AddEncryptionKey adds an encryption policy key to a kernel keyring. It uses // either the filesystem keyring for the target Mount or the user keyring for // the target User. @@ -94,7 +104,7 @@ func AddEncryptionKey(key *crypto.Key, descriptor string, options *Options) erro if shouldUseFsKeyring(descriptor, options) { return fsAddEncryptionKey(key, descriptor, options.Mount, options.User) } - return userAddKey(key, options.Service+descriptor, options.User) + return userAddKey(key, buildKeyDescription(options, descriptor), options.User) } // RemoveEncryptionKey removes an encryption policy key from a kernel keyring. @@ -108,7 +118,7 @@ func RemoveEncryptionKey(descriptor string, options *Options, allUsers bool) err } return fsRemoveEncryptionKey(descriptor, options.Mount, user) } - return userRemoveKey(options.Service+descriptor, options.User) + return userRemoveKey(buildKeyDescription(options, descriptor), options.User) } // KeyStatus is an enum that represents the status of a key in a kernel keyring. @@ -147,7 +157,7 @@ func GetEncryptionKeyStatus(descriptor string, options *Options) (KeyStatus, err if shouldUseFsKeyring(descriptor, options) { return fsGetEncryptionKeyStatus(descriptor, options.Mount, options.User) } - _, err := userFindKey(options.Service+descriptor, options.User) + _, err := userFindKey(buildKeyDescription(options, descriptor), options.User) if err != nil { return KeyAbsent, nil } 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 c123f5a24de5a25185653de8e6f970184fde035d Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 17 Mar 2020 21:10:58 -0700 Subject: Improve error message when setting v2 policy is unsupported If trying to encrypt a directory using a v2 policy fails due to the kernel lacking support for v2 policies, show a better error message. One way this can happen is if someone runs 'fscrypt setup' with a new kernel and then downgrades to an old kernel. Before: # echo -n hunter2 | fscrypt encrypt dir --source=custom_passphrase --name=foo --quiet [ERROR] fscrypt encrypt: inappropriate ioctl for device: system error: could not add key to the keyring After: # echo -n hunter2 | fscrypt encrypt dir --source=custom_passphrase --name=foo --quiet [ERROR] fscrypt encrypt: kernel is too old to support v2 encryption policies v2 encryption policies are only supported by kernel version 5.4 and later. Either use a newer kernel, or change policy_version to 1 in /etc/fscrypt.conf. --- cmd/fscrypt/errors.go | 4 ++++ keyring/keyring.go | 48 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 16 deletions(-) (limited to 'keyring') diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index bef6c2a..c242552 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -113,6 +113,10 @@ func getErrorSuggestions(err error) string { return fmt.Sprintf(`You can only use %s to access the user keyring of another user if you are running as root.`, shortDisplay(userFlag)) + case keyring.ErrV2PoliciesUnsupported: + return fmt.Sprintf(`v2 encryption policies are only supported by kernel + version 5.4 and later. Either use a newer kernel, or change + policy_version to 1 in %s.`, actions.ConfigFileLocation) case actions.ErrBadConfigFile: return `Run "sudo fscrypt setup" to recreate the file.` case actions.ErrNoConfigFile: diff --git a/keyring/keyring.go b/keyring/keyring.go index f873bac..e232de3 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -43,15 +43,16 @@ import ( // Keyring error values var ( - ErrKeyAdd = util.SystemError("could not add key to the keyring") - ErrKeyRemove = util.SystemError("could not remove key from the keyring") - ErrKeyNotPresent = errors.New("key not present or already removed") - ErrKeyFilesOpen = errors.New("some files using the key are still open") - ErrKeyAddedByOtherUsers = errors.New("other users have added the key too") - ErrKeySearch = errors.New("could not find key with descriptor") - ErrSessionUserKeying = errors.New("user keyring not linked into session keyring") - ErrAccessUserKeyring = errors.New("could not access user keyring") - ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring") + ErrKeyAdd = util.SystemError("could not add key to the keyring") + ErrKeyRemove = util.SystemError("could not remove key from the keyring") + ErrKeyNotPresent = errors.New("key not present or already removed") + ErrKeyFilesOpen = errors.New("some files using the key are still open") + ErrKeyAddedByOtherUsers = errors.New("other users have added the key too") + ErrKeySearch = errors.New("could not find key with descriptor") + ErrSessionUserKeying = errors.New("user keyring not linked into session keyring") + ErrAccessUserKeyring = errors.New("could not access user keyring") + ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring") + ErrV2PoliciesUnsupported = errors.New("kernel is too old to support v2 encryption policies") ) // Options are the options which specify *which* keyring the key should be @@ -69,16 +70,19 @@ type Options struct { UseFsKeyringForV1Policies bool } -func shouldUseFsKeyring(descriptor string, options *Options) bool { +func shouldUseFsKeyring(descriptor string, options *Options) (bool, error) { // For v1 encryption policy keys, use the filesystem keyring if // 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) + 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. - return true + if !isFsKeyringSupported(options.Mount) { + return true, ErrV2PoliciesUnsupported + } + return true, nil } // buildKeyDescription builds the description for an fscrypt key of type @@ -101,7 +105,11 @@ func AddEncryptionKey(key *crypto.Key, descriptor string, options *Options) erro if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { return errors.Wrap(err, "policy key") } - if shouldUseFsKeyring(descriptor, options) { + useFsKeyring, err := shouldUseFsKeyring(descriptor, options) + if err != nil { + return err + } + if useFsKeyring { return fsAddEncryptionKey(key, descriptor, options.Mount, options.User) } return userAddKey(key, buildKeyDescription(options, descriptor), options.User) @@ -111,7 +119,11 @@ func AddEncryptionKey(key *crypto.Key, descriptor string, options *Options) erro // It uses either the filesystem keyring for the target Mount or the user // keyring for the target User. func RemoveEncryptionKey(descriptor string, options *Options, allUsers bool) error { - if shouldUseFsKeyring(descriptor, options) { + useFsKeyring, err := shouldUseFsKeyring(descriptor, options) + if err != nil { + return err + } + if useFsKeyring { user := options.User if allUsers { user = nil @@ -154,10 +166,14 @@ func (status KeyStatus) String() string { // kernel keyring. It uses either the filesystem keyring for the target Mount // or the user keyring for the target User. func GetEncryptionKeyStatus(descriptor string, options *Options) (KeyStatus, error) { - if shouldUseFsKeyring(descriptor, options) { + useFsKeyring, err := shouldUseFsKeyring(descriptor, options) + if err != nil { + return KeyStatusUnknown, err + } + if useFsKeyring { return fsGetEncryptionKeyStatus(descriptor, options.Mount, options.User) } - _, err := userFindKey(buildKeyDescription(options, descriptor), options.User) + _, err = userFindKey(buildKeyDescription(options, descriptor), options.User) if err != nil { return KeyAbsent, nil } -- 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') 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