diff options
| -rw-r--r-- | actions/policy.go | 134 | ||||
| -rw-r--r-- | cli-tests/t_unlock.out | 38 | ||||
| -rwxr-xr-x | cli-tests/t_unlock.sh | 13 | ||||
| -rw-r--r-- | cmd/fscrypt/errors.go | 15 | ||||
| -rw-r--r-- | cmd/fscrypt/prompt.go | 3 | ||||
| -rw-r--r-- | filesystem/filesystem.go | 10 |
6 files changed, 161 insertions, 52 deletions
diff --git a/actions/policy.go b/actions/policy.go index 6c2aa51..a5fd481 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -34,16 +34,109 @@ import ( "github.com/google/fscrypt/util" ) -// Errors relating to Policies -var ( - ErrMissingPolicyMetadata = util.SystemError("missing policy metadata for encrypted directory") - ErrPolicyMetadataMismatch = util.SystemError("inconsistent metadata between filesystem and directory") - ErrDifferentFilesystem = errors.New("policies may only protect files on the same filesystem") - 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") -) +// ErrAccessDeniedPossiblyV2 indicates that a directory's encryption policy +// couldn't be retrieved due to "permission denied", but it looks like it's due +// to the directory using a v2 policy but the kernel not supporting it. +type ErrAccessDeniedPossiblyV2 struct { + DirPath string +} + +func (err *ErrAccessDeniedPossiblyV2) Error() string { + return fmt.Sprintf(` + failed to get encryption policy of %s: 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 %s.`, + err.DirPath, ConfigFileLocation) +} + +// ErrAlreadyProtected indicates that a policy is already protected by the given +// protector. +type ErrAlreadyProtected struct { + Policy *Policy + Protector *Protector +} + +func (err *ErrAlreadyProtected) Error() string { + return fmt.Sprintf("policy %s is already protected by protector %s", + err.Policy.Descriptor(), err.Protector.Descriptor()) +} + +// ErrDifferentFilesystem indicates that a policy can't be applied to a +// directory on a different filesystem. +type ErrDifferentFilesystem struct { + PolicyMount *filesystem.Mount + PathMount *filesystem.Mount +} + +func (err *ErrDifferentFilesystem) Error() string { + return fmt.Sprintf(`cannot apply policy from filesystem %q to a + directory on filesystem %q. Policies may only protect files on the same + filesystem.`, err.PolicyMount.Path, err.PathMount.Path) +} + +// ErrMissingPolicyMetadata indicates that a directory is encrypted but its +// policy metadata cannot be found. +type ErrMissingPolicyMetadata struct { + Mount *filesystem.Mount + DirPath string + Descriptor string +} + +func (err *ErrMissingPolicyMetadata) Error() string { + return fmt.Sprintf(`filesystem %q does not contain the policy metadata + for %q. This directory has either been encrypted with another tool (such + as e4crypt), or the file %q has been deleted.`, + err.Mount.Path, err.DirPath, + err.Mount.PolicyPath(err.Descriptor)) +} + +// ErrNotProtected indicates that the given policy is not protected by the given +// protector. +type ErrNotProtected struct { + PolicyDescriptor string + ProtectorDescriptor string +} + +func (err *ErrNotProtected) Error() string { + return fmt.Sprintf(`policy %s is not protected by protector %s`, + err.PolicyDescriptor, err.ProtectorDescriptor) +} + +// ErrOnlyProtector indicates that the last protector can't be removed from a +// policy. +type ErrOnlyProtector struct { + Policy *Policy +} + +func (err *ErrOnlyProtector) Error() string { + return fmt.Sprintf(`cannot remove the only protector from policy %s. A + policy must have at least one protector.`, err.Policy.Descriptor()) +} + +// ErrPolicyMetadataMismatch indicates that the policy metadata for an encrypted +// directory is inconsistent with that directory. +type ErrPolicyMetadataMismatch struct { + DirPath string + Mount *filesystem.Mount + PathData *metadata.PolicyData + MountData *metadata.PolicyData +} + +func (err *ErrPolicyMetadataMismatch) Error() string { + return fmt.Sprintf(`inconsistent metadata between encrypted directory %q + and its corresponding metadata file %q. + + Directory has descriptor:%s %s + + Metadata file has descriptor:%s %s`, + err.DirPath, err.Mount.PolicyPath(err.PathData.KeyDescriptor), + err.PathData.KeyDescriptor, err.PathData.Options, + err.MountData.KeyDescriptor, err.MountData.Options) +} // PurgeAllPolicies removes all policy keys on the filesystem from the kernel // keyring. In order for this to fully take effect, the filesystem may also need @@ -161,7 +254,7 @@ func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) { if os.IsPermission(err) && filesystem.HaveReadAccessTo(path) && !keyring.IsFsKeyringSupported(ctx.Mount) { - return nil, errors.Wrapf(ErrAccessDeniedPossiblyV2, "open %s", path) + return nil, &ErrAccessDeniedPossiblyV2{path} } return nil, err } @@ -171,14 +264,13 @@ func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) { mountData, err := ctx.Mount.GetPolicy(descriptor) if err != nil { log.Printf("getting policy metadata: %v", err) - return nil, errors.Wrap(ErrMissingPolicyMetadata, path) + return nil, &ErrMissingPolicyMetadata{ctx.Mount, path, descriptor} } log.Printf("found data for policy %s on %q", descriptor, ctx.Mount.Path) - if !proto.Equal(pathData.Options, mountData.Options) { - log.Printf("options from path: %+v", pathData.Options) - log.Printf("options from mount: %+v", mountData.Options) - return nil, errors.Wrapf(ErrPolicyMetadataMismatch, "policy %s", descriptor) + if !proto.Equal(pathData.Options, mountData.Options) || + pathData.KeyDescriptor != mountData.KeyDescriptor { + return nil, &ErrPolicyMetadataMismatch{path, ctx.Mount, pathData, mountData} } log.Print("data from filesystem and path agree") @@ -290,7 +382,7 @@ func (policy *Policy) UnlockWithProtector(protector *Protector) error { } idx, ok := policy.findWrappedKeyIndex(protector.Descriptor()) if !ok { - return ErrNotProtected + return &ErrNotProtected{policy.Descriptor(), protector.Descriptor()} } var err error @@ -321,7 +413,7 @@ func (policy *Policy) UsesProtector(protector *Protector) bool { // protector must both be unlocked. func (policy *Policy) AddProtector(protector *Protector) error { if policy.UsesProtector(protector) { - return ErrAlreadyProtected + return &ErrAlreadyProtected{policy, protector} } if policy.key == nil || protector.key == nil { return ErrLocked @@ -372,11 +464,11 @@ func (policy *Policy) AddProtector(protector *Protector) error { func (policy *Policy) RemoveProtector(protector *Protector) error { idx, ok := policy.findWrappedKeyIndex(protector.Descriptor()) if !ok { - return ErrNotProtected + return &ErrNotProtected{policy.Descriptor(), protector.Descriptor()} } if len(policy.data.WrappedPolicyKeys) == 1 { - return ErrOnlyProtector + return &ErrOnlyProtector{policy} } // Remove the wrapped key from the data @@ -397,7 +489,7 @@ func (policy *Policy) Apply(path string) error { if pathMount, err := filesystem.FindMount(path); err != nil { return err } else if pathMount != policy.Context.Mount { - return ErrDifferentFilesystem + return &ErrDifferentFilesystem{policy.Context.Mount, pathMount} } return metadata.SetPolicy(path, policy.data) diff --git a/cli-tests/t_unlock.out b/cli-tests/t_unlock.out index 29a10dd..710b063 100644 --- a/cli-tests/t_unlock.out +++ b/cli-tests/t_unlock.out @@ -81,21 +81,39 @@ contents desc1 Yes desc2 # Try to unlock with corrupt policy metadata -[ERROR] fscrypt unlock: MNT/dir: system error: missing - policy metadata for encrypted directory - -This file or directory has either been encrypted with another tool (such as -e4crypt) or the corresponding filesystem metadata has been deleted. +[ERROR] fscrypt unlock: filesystem "MNT" does not contain + the policy metadata for "MNT/dir". + This directory has either been encrypted with another + tool (such as e4crypt), or the file + "MNT/.fscrypt/policies/desc1" + has been deleted. # Try to unlock with missing policy metadata -[ERROR] fscrypt unlock: MNT/dir: system error: missing - policy metadata for encrypted directory - -This file or directory has either been encrypted with another tool (such as -e4crypt) or the corresponding filesystem metadata has been deleted. +[ERROR] fscrypt unlock: filesystem "MNT" does not contain + the policy metadata for "MNT/dir". + This directory has either been encrypted with another + tool (such as e4crypt), or the file + "MNT/.fscrypt/policies/desc20" + has been deleted. # Try to unlock with missing protector metadata [ERROR] fscrypt unlock: could not load any protectors You may need to mount a linked filesystem. Run with --verbose for more information. + +# Try to unlock with wrong policy metadata +[ERROR] fscrypt unlock: inconsistent metadata between encrypted directory + "MNT/dir1" and its corresponding + metadata file + "MNT/.fscrypt/policies/desc21". + + Directory has + descriptor:desc21 padding:32 + contents:AES_256_XTS filenames:AES_256_CTS + policy_version:2 + + Metadata file has + descriptor:desc23 padding:32 + contents:AES_256_XTS filenames:AES_256_CTS + policy_version:2 diff --git a/cli-tests/t_unlock.sh b/cli-tests/t_unlock.sh index 3dfba41..e32b0f7 100755 --- a/cli-tests/t_unlock.sh +++ b/cli-tests/t_unlock.sh @@ -67,3 +67,16 @@ mkdir "$dir" echo hunter2 | fscrypt encrypt --quiet --name=prot --skip-unlock "$dir" rm "$MNT"/.fscrypt/protectors/* _expect_failure "echo hunter2 | fscrypt unlock '$dir'" + +_print_header "Try to unlock with wrong policy metadata" +_reset_filesystems +mkdir "$MNT/dir1" +mkdir "$MNT/dir2" +echo hunter2 | fscrypt encrypt --quiet --name=dir1 --skip-unlock "$MNT/dir1" +echo hunter2 | fscrypt encrypt --quiet --name=dir2 --skip-unlock "$MNT/dir2" +policy1=$(find "$MNT/.fscrypt/policies/" -type f | head -1) +policy2=$(find "$MNT/.fscrypt/policies/" -type f | tail -1) +mv "$policy1" "$TMPDIR/policy" +mv "$policy2" "$policy1" +mv "$TMPDIR/policy" "$policy2" +_expect_failure "echo hunter2 | fscrypt unlock '$MNT/dir1'" diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index 829a9d2..3e5beed 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -127,21 +127,6 @@ func getErrorSuggestions(err error) string { 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.ErrMissingPolicyMetadata: - return `This file or directory has either been encrypted with - another tool (such as e4crypt) or the corresponding - filesystem metadata has been deleted.` - case actions.ErrPolicyMetadataMismatch: - return `The metadata for this encrypted directory is in an - inconsistent state. This most likely means the filesystem - metadata is corrupted.` - 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/cmd/fscrypt/prompt.go b/cmd/fscrypt/prompt.go index b854fb9..210d7bc 100644 --- a/cmd/fscrypt/prompt.go +++ b/cmd/fscrypt/prompt.go @@ -318,7 +318,8 @@ func optionFn(policyDescriptor string, options []*actions.ProtectorOption) (int, return idx, nil } } - return 0, actions.ErrNotProtected + return 0, &actions.ErrNotProtected{PolicyDescriptor: policyDescriptor, + ProtectorDescriptor: protector.Descriptor()} } log.Printf("optionFn(%s)", policyDescriptor) diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index e01f9ff..eb49182 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -181,9 +181,9 @@ func (m *Mount) PolicyDir() string { return filepath.Join(m.BaseDir(), policyDirName) } -// policyPath returns the full path to a regular policy file with the +// PolicyPath returns the full path to a regular policy file with the // specified descriptor. -func (m *Mount) policyPath(descriptor string) string { +func (m *Mount) PolicyPath(descriptor string) string { return filepath.Join(m.PolicyDir(), descriptor) } @@ -512,7 +512,7 @@ func (m *Mount) AddPolicy(data *metadata.PolicyData) error { return err } - return m.err(m.addMetadata(m.policyPath(data.KeyDescriptor), data)) + return m.err(m.addMetadata(m.PolicyPath(data.KeyDescriptor), data)) } // GetPolicy looks up the policy metadata by descriptor. @@ -521,7 +521,7 @@ func (m *Mount) GetPolicy(descriptor string) (*metadata.PolicyData, error) { return nil, err } data := new(metadata.PolicyData) - return data, m.err(m.getMetadata(m.policyPath(descriptor), data)) + return data, m.err(m.getMetadata(m.PolicyPath(descriptor), data)) } // RemovePolicy deletes the policy metadata from the filesystem storage. @@ -529,7 +529,7 @@ func (m *Mount) RemovePolicy(descriptor string) error { if err := m.CheckSetup(); err != nil { return err } - return m.err(m.removeMetadata(m.policyPath(descriptor))) + return m.err(m.removeMetadata(m.PolicyPath(descriptor))) } // ListPolicies lists the descriptors of all policies on this filesystem. |