aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/policy.go11
-rw-r--r--cmd/fscrypt/errors.go7
-rw-r--r--filesystem/path.go6
-rw-r--r--filesystem/path_test.go27
-rw-r--r--keyring/fs_keyring.go4
-rw-r--r--keyring/keyring.go4
-rw-r--r--keyring/keyring_test.go2
7 files changed, 56 insertions, 5 deletions
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