aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/config.go50
-rw-r--r--actions/context.go12
-rw-r--r--actions/policy.go141
-rw-r--r--actions/protector.go56
-rw-r--r--actions/recovery.go4
-rw-r--r--cli-tests/t_encrypt.out45
-rw-r--r--cli-tests/t_encrypt_custom.out6
-rw-r--r--cli-tests/t_encrypt_login.out14
-rw-r--r--cli-tests/t_encrypt_raw_key.out4
-rw-r--r--cli-tests/t_lock.out22
-rw-r--r--cli-tests/t_not_enabled.out54
-rwxr-xr-xcli-tests/t_not_enabled.sh5
-rw-r--r--cli-tests/t_not_supported.out6
-rw-r--r--cli-tests/t_setup.out16
-rw-r--r--cli-tests/t_status.out28
-rw-r--r--cli-tests/t_unlock.out35
-rwxr-xr-xcli-tests/t_unlock.sh13
-rw-r--r--cli-tests/t_v1_policy.out20
-rw-r--r--cli-tests/t_v1_policy_fs_keyring.out4
-rw-r--r--cmd/fscrypt/commands.go68
-rw-r--r--cmd/fscrypt/errors.go230
-rw-r--r--cmd/fscrypt/format.go13
-rw-r--r--cmd/fscrypt/keys.go6
-rw-r--r--cmd/fscrypt/prompt.go3
-rw-r--r--cmd/fscrypt/status.go12
-rw-r--r--crypto/crypto.go10
-rw-r--r--crypto/crypto_test.go2
-rw-r--r--crypto/key.go10
-rw-r--r--crypto/rand.go7
-rw-r--r--filesystem/filesystem.go230
-rw-r--r--filesystem/filesystem_test.go3
-rw-r--r--filesystem/mountpoint.go26
-rw-r--r--keyring/fs_keyring.go16
-rw-r--r--keyring/keyring.go10
-rw-r--r--keyring/user_keyring.go45
-rw-r--r--metadata/policy.go131
36 files changed, 953 insertions, 404 deletions
diff --git a/actions/config.go b/actions/config.go
index 2463b95..b848d92 100644
--- a/actions/config.go
+++ b/actions/config.go
@@ -22,12 +22,12 @@ package actions
import (
"bytes"
+ "fmt"
"log"
"os"
"runtime"
"time"
- "github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/google/fscrypt/crypto"
@@ -40,6 +40,46 @@ import (
// overridden by the user of this package.
var ConfigFileLocation = "/etc/fscrypt.conf"
+// ErrBadConfig is an internal error that indicates that the config struct is invalid.
+type ErrBadConfig struct {
+ Config *metadata.Config
+ UnderlyingError error
+}
+
+func (err *ErrBadConfig) Error() string {
+ return fmt.Sprintf(`internal error: config is invalid: %s
+
+ The invalid config is %s`, err.UnderlyingError, err.Config)
+}
+
+// ErrBadConfigFile indicates that the config file is invalid.
+type ErrBadConfigFile struct {
+ Path string
+ UnderlyingError error
+}
+
+func (err *ErrBadConfigFile) Error() string {
+ return fmt.Sprintf("%q is invalid: %s", err.Path, err.UnderlyingError)
+}
+
+// ErrConfigFileExists indicates that the config file already exists.
+type ErrConfigFileExists struct {
+ Path string
+}
+
+func (err *ErrConfigFileExists) Error() string {
+ return fmt.Sprintf("%q already exists", err.Path)
+}
+
+// ErrNoConfigFile indicates that the config file doesn't exist.
+type ErrNoConfigFile struct {
+ Path string
+}
+
+func (err *ErrNoConfigFile) Error() string {
+ return fmt.Sprintf("%q doesn't exist", err.Path)
+}
+
const (
// Permissions of the config file (global readable)
configPermissions = 0644
@@ -67,7 +107,7 @@ func CreateConfigFile(target time.Duration, policyVersion int64) error {
createFlags, configPermissions)
switch {
case os.IsExist(err):
- return ErrConfigFileExists
+ return &ErrConfigFileExists{ConfigFileLocation}
case err != nil:
return err
}
@@ -98,7 +138,7 @@ func getConfig() (*metadata.Config, error) {
configFile, err := os.Open(ConfigFileLocation)
switch {
case os.IsNotExist(err):
- return nil, ErrNoConfigFile
+ return nil, &ErrNoConfigFile{ConfigFileLocation}
case err != nil:
return nil, err
}
@@ -107,7 +147,7 @@ func getConfig() (*metadata.Config, error) {
log.Printf("Reading config from %q\n", ConfigFileLocation)
config, err := metadata.ReadConfig(configFile)
if err != nil {
- return nil, errors.Wrap(ErrBadConfigFile, err.Error())
+ return nil, &ErrBadConfigFile{ConfigFileLocation, err}
}
// Use system defaults if not specified
@@ -133,7 +173,7 @@ func getConfig() (*metadata.Config, error) {
}
if err := config.CheckValidity(); err != nil {
- return nil, errors.Wrap(ErrBadConfigFile, err.Error())
+ return nil, &ErrBadConfigFile{ConfigFileLocation, err}
}
return config, nil
diff --git a/actions/context.go b/actions/context.go
index 0db0671..26295ec 100644
--- a/actions/context.go
+++ b/actions/context.go
@@ -40,14 +40,8 @@ import (
"github.com/google/fscrypt/util"
)
-// Errors relating to Config files or Config structures.
-var (
- ErrNoConfigFile = errors.New("global config file does not exist")
- ErrBadConfigFile = errors.New("global config file has invalid data")
- ErrConfigFileExists = errors.New("global config file already exists")
- ErrBadConfig = errors.New("invalid Config structure provided")
- ErrLocked = errors.New("key needs to be unlocked first")
-)
+// ErrLocked indicates that the key hasn't been unwrapped yet.
+var ErrLocked = errors.New("key needs to be unlocked first")
// Context contains the necessary global state to perform most of fscrypt's
// actions.
@@ -126,7 +120,7 @@ func newContextFromUser(targetUser *user.User) (*Context, error) {
// which is being used with fscrypt.
func (ctx *Context) checkContext() error {
if err := ctx.Config.CheckValidity(); err != nil {
- return errors.Wrap(ErrBadConfig, err.Error())
+ return &ErrBadConfig{ctx.Config, err}
}
return ctx.Mount.CheckSetup()
}
diff --git a/actions/policy.go b/actions/policy.go
index 6c2aa51..6c48117 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
@@ -153,6 +246,7 @@ func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) {
// We double check that the options agree for both the data we get from
// the path, and the data we get from the mountpoint.
pathData, err := metadata.GetPolicy(path)
+ err = ctx.Mount.EncryptionSupportError(err)
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
@@ -161,7 +255,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 +265,16 @@ 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)
+ if _, ok := err.(*filesystem.ErrPolicyNotFound); ok {
+ return nil, &ErrMissingPolicyMetadata{ctx.Mount, path, descriptor}
+ }
+ return nil, err
}
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 +386,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 +417,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 +468,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,10 +493,11 @@ 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)
+ err := metadata.SetPolicy(path, policy.data)
+ return policy.Context.Mount.EncryptionSupportError(err)
}
// GetProvisioningStatus returns the status of this policy's key in the keyring.
diff --git a/actions/protector.go b/actions/protector.go
index dab9c27..3278e63 100644
--- a/actions/protector.go
+++ b/actions/protector.go
@@ -22,8 +22,7 @@ package actions
import (
"fmt"
"log"
-
- "github.com/pkg/errors"
+ "os/user"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/metadata"
@@ -34,13 +33,44 @@ import (
// This can be overridden by the user of this package.
var LoginProtectorMountpoint = "/"
-// Errors relating to Protectors
-var (
- ErrProtectorName = errors.New("login protectors do not need a name")
- ErrMissingProtectorName = errors.New("custom protectors must have a name")
- ErrDuplicateName = errors.New("protector with this name already exists")
- ErrDuplicateUID = errors.New("login protector for this user already exists")
-)
+// ErrLoginProtectorExists indicates that a user already has a login protector.
+type ErrLoginProtectorExists struct {
+ User *user.User
+}
+
+func (err *ErrLoginProtectorExists) Error() string {
+ return fmt.Sprintf("user %q already has a login protector", err.User.Username)
+}
+
+// ErrLoginProtectorName indicates that a name was given for a login protector.
+type ErrLoginProtectorName struct {
+ Name string
+ User *user.User
+}
+
+func (err *ErrLoginProtectorName) Error() string {
+ return fmt.Sprintf(`cannot assign name %q to new login protector for
+ user %q because login protectors are identified by user, not by name.`,
+ err.Name, err.User.Username)
+}
+
+// ErrMissingProtectorName indicates that a protector name is needed.
+type ErrMissingProtectorName struct {
+ Source metadata.SourceType
+}
+
+func (err *ErrMissingProtectorName) Error() string {
+ return fmt.Sprintf("%s protectors must be named", err.Source)
+}
+
+// ErrProtectorNameExists indicates that a protector name already exists.
+type ErrProtectorNameExists struct {
+ Name string
+}
+
+func (err *ErrProtectorNameExists) Error() string {
+ return fmt.Sprintf("there is already a protector named %q", err.Name)
+}
// checkForProtectorWithName returns an error if there is already a protector
// on the filesystem with a specific name (or if we cannot read the necessary
@@ -52,7 +82,7 @@ func checkForProtectorWithName(ctx *Context, name string) error {
}
for _, option := range options {
if option.Name() == name {
- return errors.Wrapf(ErrDuplicateName, "name %q", name)
+ return &ErrProtectorNameExists{name}
}
}
return nil
@@ -68,7 +98,7 @@ func checkIfUserHasLoginProtector(ctx *Context, uid int64) error {
}
for _, option := range options {
if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid {
- return errors.Wrapf(ErrDuplicateUID, "user %q", ctx.TargetUser.Username)
+ return &ErrLoginProtectorExists{ctx.TargetUser}
}
}
return nil
@@ -97,12 +127,12 @@ func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, erro
if ctx.Config.Source == metadata.SourceType_pam_passphrase {
// login protectors don't need a name (we use the username instead)
if name != "" {
- return nil, ErrProtectorName
+ return nil, &ErrLoginProtectorName{name, ctx.TargetUser}
}
} else {
// non-login protectors need a name (so we can distinguish between them)
if name == "" {
- return nil, ErrMissingProtectorName
+ return nil, &ErrMissingProtectorName{ctx.Config.Source}
}
// we don't want to duplicate naming
if err := checkForProtectorWithName(ctx, name); err != nil {
diff --git a/actions/recovery.go b/actions/recovery.go
index 1c55ec5..458349b 100644
--- a/actions/recovery.go
+++ b/actions/recovery.go
@@ -23,8 +23,6 @@ import (
"os"
"strconv"
- "github.com/pkg/errors"
-
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/metadata"
)
@@ -72,7 +70,7 @@ func AddRecoveryPassphrase(policy *Policy, dirname string) (*crypto.Key, *Protec
if err == nil {
break
}
- if errors.Cause(err) != ErrDuplicateName {
+ if _, ok := err.(*ErrProtectorNameExists); !ok {
return nil, nil, err
}
seq++
diff --git a/cli-tests/t_encrypt.out b/cli-tests/t_encrypt.out
index af38299..26cb451 100644
--- a/cli-tests/t_encrypt.out
+++ b/cli-tests/t_encrypt.out
@@ -3,19 +3,30 @@
[ERROR] fscrypt encrypt: no such file or directory
ext4 filesystem "MNT" has 0 protectors and 0 policies
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
# Try to encrypt a nonempty directory
-[ERROR] fscrypt encrypt: MNT/dir: not an empty directory
-
-Encryption can only be setup on empty directories; files cannot be encrypted
-in-place. Instead, encrypt an empty directory, copy the files into that
-encrypted directory, and securely delete the originals with "shred".
+[ERROR] fscrypt encrypt: Directory "MNT/dir" cannot be
+ encrypted because it is non-empty.
+
+Files cannot be encrypted in-place. Instead, encrypt a new directory, copy the
+files into it, and securely delete the original directory. For example:
+
+ mkdir MNT/dir.new
+ fscrypt encrypt MNT/dir.new
+ cp -a -T MNT/dir MNT/dir.new
+ find MNT/dir -type f -print0 | xargs -0 shred -n1 --remove=unlink
+ rm -rf MNT/dir
+ mv MNT/dir.new MNT/dir
+
+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
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[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
@@ -52,16 +63,16 @@ PROTECTOR LINKED DESCRIPTION
desc1 No custom protector "prot"
# Try to encrypt an already-encrypted directory
-[ERROR] fscrypt encrypt: MNT/dir: file or directory already
- encrypted
+[ERROR] fscrypt encrypt: file or directory "MNT/dir" is
+ already encrypted
# Try to encrypt another user's directory as a non-root user
-[ERROR] fscrypt encrypt: MNT/dir: you do not own this
- directory
+[ERROR] fscrypt encrypt: cannot encrypt "MNT/dir" because
+ it's owned by another user (root).
-Encryption can only be setup on directories you own, even if you have write
-permission for the directory.
+ 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
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[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 572529a..8dd15e3 100644
--- a/cli-tests/t_encrypt_custom.out
+++ b/cli-tests/t_encrypt_custom.out
@@ -46,10 +46,10 @@ PROTECTOR LINKED DESCRIPTION
desc6 No custom protector "prot"
# Try to use a custom protector without a name
-[ERROR] fscrypt encrypt: custom protectors must have a name
+[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
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[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 c6eb463..e8e0e41 100644
--- a/cli-tests/t_encrypt_login.out
+++ b/cli-tests/t_encrypt_login.out
@@ -130,13 +130,17 @@ POLICY UNLOCKED PROTECTORS
desc34 Yes desc35
# Try to give a login protector a name
-[ERROR] fscrypt encrypt: login protectors do not need a name
+[ERROR] fscrypt encrypt: cannot assign name "prot" to new login protector for
+ user "fscrypt-test-user" because login protectors are
+ 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_ROOT" has 0 protectors and 0 policies
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
# Try to use the wrong login passphrase
[ERROR] fscrypt encrypt: incorrect login passphrase
@@ -144,5 +148,5 @@ ext4 filesystem "MNT" has 0 protectors and 0 policies
ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
diff --git a/cli-tests/t_encrypt_raw_key.out b/cli-tests/t_encrypt_raw_key.out
index c7c46eb..8765ba2 100644
--- a/cli-tests/t_encrypt_raw_key.out
+++ b/cli-tests/t_encrypt_raw_key.out
@@ -21,5 +21,5 @@ desc1 No raw key protector "prot"
[ERROR] fscrypt encrypt: TMPDIR/raw_key: key file must be 32 bytes
ext4 filesystem "MNT" has 0 protectors and 0 policies
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
diff --git a/cli-tests/t_lock.out b/cli-tests/t_lock.out
index c0f9279..b8c8dcb 100644
--- a/cli-tests/t_lock.out
+++ b/cli-tests/t_lock.out
@@ -33,11 +33,16 @@ desc2 No custom protector "prot"
contents
# Try to lock directory while files busy
-[ERROR] fscrypt lock: some files using the key are still open
+[ERROR] fscrypt lock: Directory was incompletely locked because some files are
+ still open. These files remain accessible.
-Directory was incompletely locked because some files are still open. These files
-remain accessible. Try killing any processes using files in the directory, then
-re-running 'fscrypt lock'.
+Try killing any processes using files in the directory, for example using:
+
+ find "MNT/dir" -print0 | xargs -0 fuser -k
+
+Then re-run:
+
+ fscrypt lock "MNT/dir"
# => status should be incompletely locked
"MNT/dir" is encrypted with fscrypt.
@@ -72,11 +77,12 @@ mkdir: cannot create directory 'MNT/dir/subdir': Required key not available
# Try to lock directory while other user has unlocked
Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use.
-[ERROR] fscrypt lock: other users have added the key too
+[ERROR] fscrypt lock: Directory "MNT/dir" couldn't be fully
+ locked because other user(s) have unlocked it.
+
+If you want to force the directory to be locked, use:
-Directory couldn't be fully locked because other user(s) have unlocked it. If
-you want to force the directory to be locked, use 'sudo fscrypt lock --all-users
-DIR'.
+ sudo fscrypt lock --all-users "MNT/dir"
contents
"MNT/dir" is now locked.
cat: MNT/dir/file: No such file or directory
diff --git a/cli-tests/t_not_enabled.out b/cli-tests/t_not_enabled.out
index 7d74bcf..4553891 100644
--- a/cli-tests/t_not_enabled.out
+++ b/cli-tests/t_not_enabled.out
@@ -2,28 +2,52 @@
# Disable encryption on DEV
# Try to encrypt a directory when encryption is disabled
-[ERROR] fscrypt encrypt: get encryption policy MNT/dir:
- encryption not enabled
+[ERROR] fscrypt encrypt: encryption not enabled on filesystem
+ MNT (DEV).
-Encryption is either disabled in the kernel config, or needs to be enabled for
-this filesystem. See the documentation on how to enable encryption on ext4
-systems (and the risks of doing so).
+To enable encryption support on this filesystem, run:
+
+ sudo tune2fs -O encrypt "DEV"
+
+Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation
+for more details.
# Try to unlock a directory when encryption is disabled
-[ERROR] fscrypt unlock: get encryption policy MNT/dir:
- encryption not enabled
+[ERROR] fscrypt unlock: encryption not enabled on filesystem
+ MNT (DEV).
+
+To enable encryption support on this filesystem, run:
-Encryption is either disabled in the kernel config, or needs to be enabled for
-this filesystem. See the documentation on how to enable encryption on ext4
-systems (and the risks of doing so).
+ sudo tune2fs -O encrypt "DEV"
+
+Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation
+for more details.
# Try to lock a directory when encryption is disabled
-[ERROR] fscrypt lock: get encryption policy MNT/dir:
- encryption not enabled
+[ERROR] fscrypt lock: encryption not enabled on filesystem
+ MNT (DEV).
+
+To enable encryption support on this filesystem, run:
+
+ sudo tune2fs -O encrypt "DEV"
+
+Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation
+for more details.
+
+# Check for additional message when GRUB appears to be installed
+[ERROR] fscrypt encrypt: encryption not enabled on filesystem
+ MNT (DEV).
+
+To enable encryption support on this filesystem, run:
+
+ sudo tune2fs -O encrypt "DEV"
+
+WARNING: you seem to have GRUB installed on this filesystem. Before doing the
+above, make sure you are using GRUB v2.04 or later; otherwise your system will
+become unbootable.
-Encryption is either disabled in the kernel config, or needs to be enabled for
-this filesystem. See the documentation on how to enable encryption on ext4
-systems (and the risks of doing so).
+Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation
+for more details.
# Enable encryption on DEV
diff --git a/cli-tests/t_not_enabled.sh b/cli-tests/t_not_enabled.sh
index 3c7d22c..fae1094 100755
--- a/cli-tests/t_not_enabled.sh
+++ b/cli-tests/t_not_enabled.sh
@@ -26,6 +26,11 @@ _expect_failure "fscrypt unlock '$dir'"
_print_header "Try to lock a directory when encryption is disabled"
_expect_failure "fscrypt lock '$dir'"
+_print_header "Check for additional message when GRUB appears to be installed"
+mkdir -p "$MNT/boot/grub"
+_expect_failure "fscrypt encrypt '$dir'"
+rm -r "${MNT:?}/boot"
+
_print_header "Enable encryption on $DEV"
_run_noisy_command "tune2fs -O encrypt '$DEV'"
diff --git a/cli-tests/t_not_supported.out b/cli-tests/t_not_supported.out
index 8af840c..ecee56a 100644
--- a/cli-tests/t_not_supported.out
+++ b/cli-tests/t_not_supported.out
@@ -5,7 +5,5 @@
Metadata directories created at "MNT/.fscrypt".
# Try to encrypt a directory on tmpfs
-[ERROR] fscrypt encrypt: get encryption policy MNT/dir:
- encryption not supported
-
-Encryption for this type of filesystem is not supported on this kernel version.
+[ERROR] fscrypt encrypt: This kernel doesn't support encryption on tmpfs
+ filesystems.
diff --git a/cli-tests/t_setup.out b/cli-tests/t_setup.out
index e1606ba..943a781 100644
--- a/cli-tests/t_setup.out
+++ b/cli-tests/t_setup.out
@@ -26,7 +26,7 @@ Skipping creating MNT_ROOT/.fscrypt because it already exists.
# fscrypt setup --quiet when fscrypt.conf already exists
[ERROR] fscrypt setup: operation would be destructive
-Use --force to automatically run destructive operations.
+If desired, use --force to automatically run destructive operations.
# fscrypt setup --quiet --force when fscrypt.conf already exists
@@ -34,16 +34,16 @@ Use --force to automatically run destructive operations.
Metadata directories created at "MNT/.fscrypt".
# fscrypt setup filesystem (already set up)
-[ERROR] fscrypt setup: filesystem MNT: already setup for use
- with fscrypt
+[ERROR] fscrypt setup: filesystem MNT is already setup for
+ use with fscrypt
# no config file
-[ERROR] fscrypt setup: global config file does not exist
+[ERROR] fscrypt setup: "FSCRYPT_CONF" doesn't exist
-Run "sudo fscrypt setup" to create the file.
+Run "sudo fscrypt setup" to create this file.
# bad config file
-[ERROR] fscrypt setup: invalid character 'b' looking for beginning of value:
- global config file has invalid data
+[ERROR] fscrypt setup: "FSCRYPT_CONF" is invalid: invalid
+ character 'b' looking for beginning of value
-Run "sudo fscrypt setup" to recreate the file.
+Either fix this file manually, or run "sudo fscrypt setup" to recreate it.
diff --git a/cli-tests/t_status.out b/cli-tests/t_status.out
index b036712..0d478b5 100644
--- a/cli-tests/t_status.out
+++ b/cli-tests/t_status.out
@@ -10,10 +10,10 @@ ext4 filesystem "MNT" has 0 protectors and 0 policies
# Get status of unencrypted directory on setup mountpoint
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
# Remove fscrypt metadata from MNT
@@ -24,21 +24,25 @@ ext4 supported No
ext4 supported No
# Get status of not-setup mountpoint
-[ERROR] fscrypt status: filesystem MNT: not setup for use
+[ERROR] fscrypt status: filesystem MNT is not setup for use
with fscrypt
-Run "fscrypt setup MOUNTPOINT" to use fscrypt on this filesystem.
-[ERROR] fscrypt status: filesystem MNT: not setup for use
+Run "sudo fscrypt setup MNT" to use fscrypt on this
+filesystem.
+[ERROR] fscrypt status: filesystem MNT is not setup for use
with fscrypt
-Run "fscrypt setup MOUNTPOINT" to use fscrypt on this filesystem.
+Run "sudo fscrypt setup MNT" to use fscrypt on this
+filesystem.
# Get status of unencrypted directory on not-setup mountpoint
-[ERROR] fscrypt status: filesystem MNT: not setup for use
+[ERROR] fscrypt status: filesystem MNT is not setup for use
with fscrypt
-Run "fscrypt setup MOUNTPOINT" to use fscrypt on this filesystem.
-[ERROR] fscrypt status: filesystem MNT: not setup for use
+Run "sudo fscrypt setup MNT" to use fscrypt on this
+filesystem.
+[ERROR] fscrypt status: filesystem MNT is not setup for use
with fscrypt
-Run "fscrypt setup MOUNTPOINT" to use fscrypt on this filesystem.
+Run "sudo fscrypt setup MNT" to use fscrypt on this
+filesystem.
diff --git a/cli-tests/t_unlock.out b/cli-tests/t_unlock.out
index 29a10dd..25430a0 100644
--- a/cli-tests/t_unlock.out
+++ b/cli-tests/t_unlock.out
@@ -81,21 +81,36 @@ 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: fscrypt metadata file at
+ "MNT/.fscrypt/policies/desc1"
+ is corrupt: unexpected EOF
# 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/cli-tests/t_v1_policy.out b/cli-tests/t_v1_policy.out
index 0ff5219..b47bcca 100644
--- a/cli-tests/t_v1_policy.out
+++ b/cli-tests/t_v1_policy.out
@@ -11,14 +11,15 @@ can be done with --user=USERNAME. To use the root user's keyring or passphrase,
use --user=root.
# Try to use --user=root as user
-[ERROR] fscrypt encrypt: setting uids: operation not permitted: could not access
- user keyring
+[ERROR] fscrypt encrypt: could not access user keyring for "root": setting uids:
+ operation not permitted
You can only use --user=USERNAME to access the user keyring of another user if
you are running as root.
# Try to encrypt without user keyring in session keyring
-[ERROR] fscrypt encrypt: user keyring not linked into session keyring
+[ERROR] fscrypt encrypt: user keyring for "fscrypt-test-user" is not linked into
+ the session keyring
This is usually the result of a bad PAM configuration. Either correct the
problem in your PAM stack, enable pam_keyinit.so, or run "keyctl link @u @s".
@@ -100,11 +101,16 @@ cat: MNT/dir/file: No such file or directory
# Testing incompletely locking v1-encrypted directory
Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use.
Encrypted data removed from filesystem cache.
-[ERROR] fscrypt lock: some files using the key are still open
+[ERROR] fscrypt lock: Directory was incompletely locked because some files are
+ still open. These files remain accessible.
-Directory was incompletely locked because some files are still open. These files
-remain accessible. Try killing any processes using files in the directory, then
-re-running 'fscrypt lock'.
+Try killing any processes using files in the directory, for example using:
+
+ find "MNT/dir" -print0 | xargs -0 fuser -k
+
+Then re-run:
+
+ fscrypt lock "MNT/dir"
"MNT/dir" is encrypted with fscrypt.
Policy: desc1
diff --git a/cli-tests/t_v1_policy_fs_keyring.out b/cli-tests/t_v1_policy_fs_keyring.out
index ca32ec1..cfc8f7c 100644
--- a/cli-tests/t_v1_policy_fs_keyring.out
+++ b/cli-tests/t_v1_policy_fs_keyring.out
@@ -10,8 +10,8 @@ Either this command should be run as root, or you should set
re-create your encrypted directories using v2 encryption policies rather than v1
(this requires setting '"policy_version": "2"' in the "options" section of
/etc/fscrypt.conf).
-[ERROR] fscrypt status: get encryption policy MNT/dir: file
- or directory not encrypted
+[ERROR] fscrypt status: file or directory "MNT/dir" is not
+ encrypted
# Encrypt directory as user with --skip-unlock
"MNT/dir" is encrypted with fscrypt.
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index 51cf136..8058cb3 100644
--- a/cmd/fscrypt/commands.go
+++ b/cmd/fscrypt/commands.go
@@ -74,7 +74,7 @@ func setupAction(c *cli.Context) error {
return newExitError(c, err)
}
if err := setupFilesystem(c.App.Writer, actions.LoginProtectorMountpoint); err != nil {
- if errors.Cause(err) != filesystem.ErrAlreadySetup {
+ if _, ok := err.(*filesystem.ErrAlreadySetup); !ok {
return newExitError(c, err)
}
fmt.Fprintf(c.App.Writer,
@@ -282,11 +282,7 @@ func encryptPath(path string) (err error) {
}
}()
}
- if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) {
- // EACCES at this point indicates ownership issues.
- err = errors.Wrap(ErrBadOwners, path)
- }
- if err != nil {
+ if err = policy.Apply(path); err != nil {
return
}
if recoveryPassphrase != nil {
@@ -301,7 +297,18 @@ func encryptPath(path string) (err error) {
// checkEncryptable returns an error if the path cannot be encrypted.
func checkEncryptable(ctx *actions.Context, path string) error {
- log.Printf("ensuring %s is an empty and readable directory", path)
+
+ log.Printf("checking whether %q is already encrypted", path)
+ if _, err := metadata.GetPolicy(path); err == nil {
+ return &metadata.ErrAlreadyEncrypted{Path: path}
+ }
+
+ log.Printf("checking whether filesystem %s supports encryption", ctx.Mount.Path)
+ if err := ctx.Mount.CheckSupport(); err != nil {
+ return err
+ }
+
+ log.Printf("checking whether %q is an empty and readable directory", path)
f, err := os.Open(path)
if err != nil {
return err
@@ -311,25 +318,13 @@ func checkEncryptable(ctx *actions.Context, path string) error {
switch names, err := f.Readdirnames(-1); {
case err != nil:
// Could not read directory (might not be a directory)
- log.Print(errors.Wrap(err, path))
- return errors.Wrap(ErrNotEmptyDir, path)
- case len(names) > 0:
- log.Printf("directory %s is not empty", path)
- return errors.Wrap(ErrNotEmptyDir, path)
- }
-
- log.Printf("ensuring %s supports encryption and filesystem is using fscrypt", path)
- switch _, err := actions.GetPolicyFromPath(ctx, path); errors.Cause(err) {
- case metadata.ErrNotEncrypted:
- // We are not encrypted. Finally, we check that the filesystem
- // supports encryption
- return ctx.Mount.CheckSupport()
- case nil:
- // We are encrypted
- return errors.Wrap(metadata.ErrEncrypted, path)
- default:
+ err = errors.Wrap(err, path)
+ log.Print(err)
return err
+ case len(names) > 0:
+ return &ErrDirNotEmpty{path}
}
+ return err
}
// selectOrCreateProtector uses user input (or flags) to either create a new
@@ -413,7 +408,7 @@ func unlockAction(c *cli.Context) error {
if policy.IsProvisionedByTargetUser() {
log.Printf("policy %s is already provisioned by %v",
policy.Descriptor(), ctx.TargetUser.Username)
- return newExitError(c, errors.Wrapf(ErrPolicyUnlocked, path))
+ return newExitError(c, errors.Wrapf(ErrDirAlreadyUnlocked, path))
}
if err := policy.Unlock(optionFn, existingKeyFn); err != nil {
@@ -502,7 +497,14 @@ func lockAction(c *cli.Context) error {
}
if err = policy.Deprovision(allUsersFlag.Value); err != nil {
- if err != keyring.ErrKeyNotPresent {
+ switch err {
+ case keyring.ErrKeyNotPresent:
+ break
+ case keyring.ErrKeyAddedByOtherUsers:
+ return newExitError(c, &ErrDirUnlockedByOtherUsers{path})
+ case keyring.ErrKeyFilesOpen:
+ return newExitError(c, &ErrDirFilesOpen{path})
+ default:
return newExitError(c, err)
}
// Key is no longer present. Normally that means the directory
@@ -513,7 +515,7 @@ func lockAction(c *cli.Context) error {
// locking the directory by dropping caches again.
if !policy.NeedsUserKeyring() || !isDirUnlockedHeuristic(path) {
log.Printf("policy %s is already fully deprovisioned", policy.Descriptor())
- return newExitError(c, errors.Wrapf(ErrPolicyLocked, path))
+ return newExitError(c, errors.Wrapf(ErrDirAlreadyLocked, path))
}
}
@@ -522,7 +524,7 @@ func lockAction(c *cli.Context) error {
return newExitError(c, err)
}
if isDirUnlockedHeuristic(path) {
- return newExitError(c, keyring.ErrKeyFilesOpen)
+ return newExitError(c, &ErrDirFilesOpen{path})
}
}
@@ -663,17 +665,15 @@ func statusAction(c *cli.Context) error {
err = writeGlobalStatus(c.App.Writer)
case 1:
path := c.Args().Get(0)
- ctx, mntErr := actions.NewContextFromMountpoint(path, nil)
- switch errors.Cause(mntErr) {
- case nil:
+ var ctx *actions.Context
+ ctx, err = actions.NewContextFromMountpoint(path, nil)
+ if err == nil {
// Case (2) - mountpoint status
err = writeFilesystemStatus(c.App.Writer, ctx)
- case filesystem.ErrNotAMountpoint:
+ } else if _, ok := err.(*filesystem.ErrNotAMountpoint); ok {
// Case (3) - file or directory status
err = writePathStatus(c.App.Writer, path)
- default:
- err = mntErr
}
default:
return expectedArgsErr(c, 1, true)
diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go
index 8bda921..63ddaf4 100644
--- a/cmd/fscrypt/errors.go
+++ b/cmd/fscrypt/errors.go
@@ -30,6 +30,7 @@ import (
"github.com/pkg/errors"
"github.com/urfave/cli"
+ "golang.org/x/sys/unix"
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/crypto"
@@ -46,7 +47,6 @@ const failureExitCode = 1
var (
ErrCanceled = errors.New("operation canceled")
ErrNoDestructiveOps = errors.New("operation would be destructive")
- ErrMaxPassphrase = util.SystemError("max passphrase length exceeded")
ErrInvalidSource = errors.New("invalid source type")
ErrPassphraseMismatch = errors.New("entered passphrases do not match")
ErrSpecifyProtector = errors.New("multiple protectors available")
@@ -55,10 +55,8 @@ var (
ErrKeyFileLength = errors.Errorf("key file must be %d bytes", metadata.InternalKeyLen)
ErrAllLoadsFailed = errors.New("could not load any protectors")
ErrMustBeRoot = errors.New("this command must be run as root")
- ErrPolicyUnlocked = errors.New("this file or directory is already unlocked")
- ErrPolicyLocked = errors.New("this file or directory is already locked")
- ErrBadOwners = errors.New("you do not own this directory")
- ErrNotEmptyDir = errors.New("not an empty directory")
+ ErrDirAlreadyUnlocked = errors.New("this file or directory is already unlocked")
+ ErrDirAlreadyLocked = errors.New("this file or directory is already locked")
ErrNotPassphrase = errors.New("protector does not use a passphrase")
ErrUnknownUser = errors.New("unknown user")
ErrDropCachesPerm = errors.New("inode cache can only be dropped as root")
@@ -66,6 +64,38 @@ var (
ErrFsKeyringPerm = errors.New("root is required to add/remove v1 encryption policy keys to/from filesystem")
)
+// ErrDirFilesOpen indicates that a directory can't be fully locked because
+// files protected by the directory's policy are still open.
+type ErrDirFilesOpen struct {
+ DirPath string
+}
+
+func (err *ErrDirFilesOpen) Error() string {
+ return fmt.Sprintf(`Directory was incompletely locked because some files
+ are still open. These files remain accessible.`)
+}
+
+// ErrDirUnlockedByOtherUsers indicates that a directory can't be locked because
+// the directory's policy is still provisioned by other users.
+type ErrDirUnlockedByOtherUsers struct {
+ DirPath string
+}
+
+func (err *ErrDirUnlockedByOtherUsers) Error() string {
+ return fmt.Sprintf(`Directory %q couldn't be fully locked because other
+ user(s) have unlocked it.`, err.DirPath)
+}
+
+// ErrDirNotEmpty indicates that a directory can't be encrypted because it's not
+// empty.
+type ErrDirNotEmpty struct {
+ DirPath string
+}
+
+func (err *ErrDirNotEmpty) Error() string {
+ return fmt.Sprintf("Directory %q cannot be encrypted because it is non-empty.", err.DirPath)
+}
+
var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag))
// getFullName returns the full name of the application or command being used.
@@ -76,82 +106,156 @@ func getFullName(c *cli.Context) string {
return c.App.HelpName
}
+func suggestEnablingEncryption(mnt *filesystem.Mount) string {
+ kconfig := "CONFIG_FS_ENCRYPTION=y"
+ switch mnt.FilesystemType {
+ case "ext4":
+ // Recommend running tune2fs -O encrypt. But be really careful;
+ // old kernels didn't support block_size != PAGE_SIZE, and old
+ // GRUB didn't support encryption.
+ var statfs unix.Statfs_t
+ if err := unix.Statfs(mnt.Path, &statfs); err != nil {
+ return ""
+ }
+ pagesize := int64(os.Getpagesize())
+ if statfs.Bsize != pagesize && !util.IsKernelVersionAtLeast(5, 5) {
+ return fmt.Sprintf(`This filesystem uses a block size
+ (%d) other than the system page size (%d). Ext4
+ encryption didn't support this case until kernel v5.5.
+ Do *not* enable encryption on this filesystem. Either
+ upgrade your kernel to v5.5 or later, or re-create this
+ filesystem using 'mkfs.ext4 -b %d -O encrypt %s'
+ (WARNING: that will erase all data on it).`,
+ statfs.Bsize, pagesize, pagesize, mnt.Device)
+ }
+ if !util.IsKernelVersionAtLeast(5, 1) {
+ kconfig = "CONFIG_EXT4_ENCRYPTION=y"
+ }
+ s := fmt.Sprintf(`To enable encryption support on this
+ filesystem, run:
+
+ > sudo tune2fs -O encrypt %q
+ `, mnt.Device)
+ if _, err := os.Stat(filepath.Join(mnt.Path, "boot/grub")); err == nil {
+ s += `
+ WARNING: you seem to have GRUB installed on this
+ filesystem. Before doing the above, make sure you are
+ using GRUB v2.04 or later; otherwise your system will
+ become unbootable.
+ `
+ }
+ s += fmt.Sprintf(`
+ Also ensure that your kernel has %s. See the documentation for
+ more details.`, kconfig)
+ return s
+ case "f2fs":
+ if !util.IsKernelVersionAtLeast(5, 1) {
+ kconfig = "CONFIG_F2FS_FS_ENCRYPTION=y"
+ }
+ return fmt.Sprintf(`To enable encryption support on this
+ filesystem, you'll need to run:
+
+ > sudo fsck.f2fs -O encrypt %q
+
+ Also ensure that your kernel has %s. See the documentation for
+ more details.`, mnt.Device, kconfig)
+ default:
+ return `See the documentation for how to enable encryption
+ support on this filesystem.`
+ }
+}
+
// getErrorSuggestions returns a string containing suggestions about how to fix
// an error. If no suggestion is necessary or available, return empty string.
func getErrorSuggestions(err error) string {
+ switch e := err.(type) {
+ case *ErrDirFilesOpen:
+ return fmt.Sprintf(`Try killing any processes using files in the
+ directory, for example using:
+
+ > find %q -print0 | xargs -0 fuser -k
+
+ Then re-run:
+
+ > fscrypt lock %q`, e.DirPath, e.DirPath)
+ case *ErrDirNotEmpty:
+ dir := e.DirPath
+ newDir := dir + ".new"
+ return fmt.Sprintf(`Files cannot be encrypted in-place. Instead,
+ encrypt a new directory, copy the files into it, and securely
+ delete the original directory. For example:
+
+ > mkdir %s
+ > fscrypt encrypt %s
+ > cp -a -T %s %s
+ > find %s -type f -print0 | xargs -0 shred -n1 --remove=unlink
+ > rm -rf %s
+ > mv %s %s
+
+ 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.`, newDir, newDir, dir, newDir, dir, dir, newDir, dir)
+ case *ErrDirUnlockedByOtherUsers:
+ return fmt.Sprintf(`If you want to force the directory to be
+ locked, use:
+
+ > sudo fscrypt lock --all-users %q`, e.DirPath)
+ case *actions.ErrBadConfigFile:
+ return `Either fix this file manually, or run "sudo fscrypt setup" to recreate it.`
+ case *actions.ErrLoginProtectorName:
+ return fmt.Sprintf("To fix this, don't specify the %s option.", shortDisplay(nameFlag))
+ case *actions.ErrMissingProtectorName:
+ return fmt.Sprintf("Use %s to specify a protector name.", shortDisplay(nameFlag))
+ case *actions.ErrNoConfigFile:
+ return `Run "sudo fscrypt setup" to create this file.`
+ case *filesystem.ErrEncryptionNotEnabled:
+ return suggestEnablingEncryption(e.Mount)
+ case *filesystem.ErrEncryptionNotSupported:
+ switch e.Mount.FilesystemType {
+ case "ext4":
+ if !util.IsKernelVersionAtLeast(4, 1) {
+ return "ext4 encryption requires kernel v4.1 or later."
+ }
+ case "f2fs":
+ if !util.IsKernelVersionAtLeast(4, 2) {
+ return "f2fs encryption requires kernel v4.2 or later."
+ }
+ case "ubifs":
+ if !util.IsKernelVersionAtLeast(4, 10) {
+ return "ubifs encryption requires kernel v4.10 or later."
+ }
+ }
+ return ""
+ case *filesystem.ErrNotSetup:
+ return fmt.Sprintf(`Run "sudo fscrypt setup %s" to use fscrypt
+ on this filesystem.`, e.Mount.Path)
+ case *keyring.ErrAccessUserKeyring:
+ 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.ErrSessionUserKeyring:
+ return `This is usually the result of a bad PAM configuration.
+ Either correct the problem in your PAM stack, enable
+ pam_keyinit.so, or run "keyctl link @u @s".`
+ }
switch errors.Cause(err) {
- case filesystem.ErrNotSetup:
- return fmt.Sprintf(`Run "fscrypt setup %s" to use fscrypt on this filesystem.`, mountpointArg)
- case crypto.ErrKeyLock:
+ case crypto.ErrMlockUlimit:
return `Too much memory was requested to be locked in RAM. The
current limit for this user can be checked with "ulimit
-l". The limit can be modified by either changing the
"memlock" item in /etc/security/limits.conf or by
changing the "LimitMEMLOCK" value in systemd.`
- case metadata.ErrEncryptionNotSupported:
- return `Encryption for this type of filesystem is not supported
- on this kernel version.`
- case metadata.ErrEncryptionNotEnabled:
- return `Encryption is either disabled in the kernel config, or
- needs to be enabled for this filesystem. See the
- documentation on how to enable encryption on ext4
- systems (and the risks of doing so).`
- case keyring.ErrKeyFilesOpen:
- return `Directory was incompletely locked because some files are
- still open. These files remain accessible. Try killing
- any processes using files in the directory, then
- re-running 'fscrypt lock'.`
- case keyring.ErrKeyAddedByOtherUsers:
- return `Directory couldn't be fully locked because other user(s)
- have unlocked it. If you want to force the directory to
- be locked, use 'sudo fscrypt lock --all-users DIR'.`
- case keyring.ErrSessionUserKeying:
- return `This is usually the result of a bad PAM configuration.
- Either correct the problem in your PAM stack, enable
- pam_keyinit.so, or run "keyctl link @u @s".`
- case keyring.ErrAccessUserKeyring:
- 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:
- return `Run "sudo fscrypt setup" to create the file.`
- 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.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))
+ return fmt.Sprintf("If desired, use %s to automatically run destructive operations.",
+ shortDisplay(forceFlag))
case ErrSpecifyProtector:
return fmt.Sprintf("Use %s to specify a protector.", shortDisplay(protectorFlag))
case ErrSpecifyKeyFile:
return fmt.Sprintf("Use %s to specify a key file.", shortDisplay(keyFileFlag))
- case ErrBadOwners:
- return `Encryption can only be setup on directories you own,
- even if you have write permission for the directory.`
- case ErrNotEmptyDir:
- return `Encryption can only be setup on empty directories; files
- cannot be encrypted in-place. Instead, encrypt an empty
- directory, copy the files into that encrypted directory,
- and securely delete the originals with "shred".`
case ErrDropCachesPerm:
return fmt.Sprintf(`Either this command should be run as root to
properly clear the inode cache, or it should be run with
diff --git a/cmd/fscrypt/format.go b/cmd/fscrypt/format.go
index cc268aa..576d025 100644
--- a/cmd/fscrypt/format.go
+++ b/cmd/fscrypt/format.go
@@ -121,7 +121,8 @@ func longDisplay(f prettyFlag, defaultString ...string) string {
// Takes an input string text, and wraps the text so that each line begins with
// padding spaces (except for the first line), ends with a newline (except the
// last line), and each line has length less than lineLength. If the text
-// contains a word which is too long, that word gets its own line.
+// contains a word which is too long, that word gets its own line. Paragraphs
+// and "code blocks" are preserved.
func wrapText(text string, padding int) string {
// We use a buffer to format the wrapped text so we get O(n) runtime
var buffer bytes.Buffer
@@ -141,10 +142,18 @@ func wrapText(text string, padding int) string {
continue
}
+ codeBlock := (words[0] == ">")
+ if codeBlock {
+ words[0] = " "
+ if filled != 0 {
+ buffer.WriteString("\n")
+ filled = 0
+ }
+ }
for _, word := range words {
wordLen := utf8.RuneCountInString(word)
// Write a newline if needed.
- if filled != 0 && filled+1+wordLen > lineLength {
+ if filled != 0 && filled+1+wordLen > lineLength && !codeBlock {
buffer.WriteString("\n")
filled = 0
}
diff --git a/cmd/fscrypt/keys.go b/cmd/fscrypt/keys.go
index 872ca2a..77e3900 100644
--- a/cmd/fscrypt/keys.go
+++ b/cmd/fscrypt/keys.go
@@ -55,14 +55,14 @@ var (
// struct is empty as the reader needs to maintain no internal state.
type passphraseReader struct{}
-// Read gets input from the terminal until a newline is encountered. This read
-// should be called with the maximum buffer size for the passphrase.
+// Read gets input from the terminal until a newline is encountered or the given
+// buffer is full.
func (p passphraseReader) Read(buf []byte) (int, error) {
// We read one byte at a time to handle backspaces
position := 0
for {
if position == len(buf) {
- return position, ErrMaxPassphrase
+ return position, nil
}
if _, err := io.ReadFull(os.Stdin, buf[position:position+1]); err != nil {
return position, err
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/cmd/fscrypt/status.go b/cmd/fscrypt/status.go
index 40bb49e..02fdc74 100644
--- a/cmd/fscrypt/status.go
+++ b/cmd/fscrypt/status.go
@@ -27,12 +27,9 @@ import (
"strings"
"text/tabwriter"
- "github.com/pkg/errors"
-
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/filesystem"
"github.com/google/fscrypt/keyring"
- "github.com/google/fscrypt/metadata"
)
// Creates a writer which correctly aligns tabs with the specified header.
@@ -46,12 +43,13 @@ func makeTableWriter(w io.Writer, header string) *tabwriter.Writer {
// encryptionStatus will be printed in the ENCRYPTION column. An empty string
// indicates the filesystem should not be printed.
func encryptionStatus(err error) string {
- switch errors.Cause(err) {
- case nil:
+ if err == nil {
return "supported"
- case metadata.ErrEncryptionNotEnabled:
+ }
+ switch err.(type) {
+ case *filesystem.ErrEncryptionNotEnabled:
return "not enabled"
- case metadata.ErrEncryptionNotSupported:
+ case *filesystem.ErrEncryptionNotSupported:
return "not supported"
default:
// Unknown error regarding support
diff --git a/crypto/crypto.go b/crypto/crypto.go
index 9a138d0..1f64b38 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -50,13 +50,9 @@ import (
// Crypto error values
var (
- ErrBadAuth = errors.New("key authentication check failed")
- ErrNegativeLength = errors.New("keys cannot have negative lengths")
- ErrRecoveryCode = errors.New("invalid recovery code")
- ErrGetrandomFail = util.SystemError("getrandom() failed")
- ErrKeyAlloc = util.SystemError("could not allocate memory for key")
- ErrKeyFree = util.SystemError("could not free memory of key")
- ErrKeyLock = errors.New("could not lock key in memory")
+ ErrBadAuth = errors.New("key authentication check failed")
+ ErrRecoveryCode = errors.New("invalid recovery code")
+ ErrMlockUlimit = errors.New("could not lock key in memory")
)
// panicInputLength panics if "name" has invalid length (expected != actual)
diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go
index 6eb0b02..10b3d17 100644
--- a/crypto/crypto_test.go
+++ b/crypto/crypto_test.go
@@ -257,7 +257,7 @@ func TestBigKeyGen(t *testing.T) {
case nil:
key.Wipe()
return
- case ErrKeyLock:
+ case ErrMlockUlimit:
// Don't fail just because "ulimit -l" is too low.
return
default:
diff --git a/crypto/key.go b/crypto/key.go
index 77adc95..2e57443 100644
--- a/crypto/key.go
+++ b/crypto/key.go
@@ -98,7 +98,7 @@ func NewBlankKey(length int) (*Key, error) {
if length == 0 {
return &Key{data: nil}, nil
} else if length < 0 {
- return nil, errors.Wrapf(ErrNegativeLength, "length of %d requested", length)
+ return nil, errors.Errorf("requested key length %d is negative", length)
}
flags := keyMmapFlags
@@ -109,11 +109,11 @@ func NewBlankKey(length int) (*Key, error) {
// See MAP_ANONYMOUS in http://man7.org/linux/man-pages/man2/mmap.2.html
data, err := unix.Mmap(-1, 0, length, keyProtection, flags)
if err == unix.EAGAIN {
- return nil, ErrKeyLock
+ return nil, ErrMlockUlimit
}
if err != nil {
- log.Printf("unix.Mmap() with length=%d failed: %v", length, err)
- return nil, ErrKeyAlloc
+ return nil, errors.Wrapf(err,
+ "failed to allocate (mmap) key buffer of length %d", length)
}
key := &Key{data: data}
@@ -139,7 +139,7 @@ func (key *Key) Wipe() error {
if err := unix.Munmap(data); err != nil {
log.Printf("unix.Munmap() failed: %v", err)
- return ErrKeyFree
+ return errors.Wrapf(err, "failed to free (munmap) key buffer")
}
}
return nil
diff --git a/crypto/rand.go b/crypto/rand.go
index 4d8c044..7d1e55b 100644
--- a/crypto/rand.go
+++ b/crypto/rand.go
@@ -90,10 +90,9 @@ func (r randReader) Read(buffer []byte) (int, error) {
case nil:
return n, nil
case unix.EAGAIN:
- return 0, errors.Wrap(ErrGetrandomFail, "insufficient entropy in pool")
+ err = errors.New("insufficient entropy in pool")
case unix.ENOSYS:
- return 0, errors.Wrap(ErrGetrandomFail, "kernel must be v3.17 or later")
- default:
- return 0, errors.Wrap(ErrGetrandomFail, err.Error())
+ err = errors.New("kernel must be v3.17 or later")
}
+ return 0, errors.Wrap(err, "getrandom() failed")
}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index e01f9ff..9b5b7e2 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -47,23 +47,90 @@ import (
"golang.org/x/sys/unix"
"github.com/google/fscrypt/metadata"
- "github.com/google/fscrypt/util"
)
-// Filesystem error values
-var (
- ErrNotAMountpoint = errors.New("not a mountpoint")
- ErrAlreadySetup = errors.New("already setup for use with fscrypt")
- ErrNotSetup = errors.New("not setup for use with fscrypt")
- ErrNoMetadata = errors.New("could not find metadata")
- ErrLinkedProtector = errors.New("not a regular protector")
- ErrInvalidMetadata = errors.New("provided metadata is invalid")
- ErrFollowLink = errors.New("cannot follow filesystem link")
- ErrLinkExpired = errors.New("no longer exists on linked filesystem")
- ErrMakeLink = util.SystemError("cannot create filesystem link")
- ErrGlobalMountInfo = util.SystemError("creating global mountpoint list failed")
- ErrCorruptMetadata = util.SystemError("on-disk metadata is corrupt")
-)
+// ErrAlreadySetup indicates that a filesystem is already setup for fscrypt.
+type ErrAlreadySetup struct {
+ Mount *Mount
+}
+
+func (err *ErrAlreadySetup) Error() string {
+ return fmt.Sprintf("filesystem %s is already setup for use with fscrypt",
+ err.Mount.Path)
+}
+
+// ErrCorruptMetadata indicates that an fscrypt metadata file is corrupt.
+type ErrCorruptMetadata struct {
+ Path string
+ UnderlyingError error
+}
+
+func (err *ErrCorruptMetadata) Error() string {
+ return fmt.Sprintf("fscrypt metadata file at %q is corrupt: %s",
+ err.Path, err.UnderlyingError)
+}
+
+// ErrFollowLink indicates that a protector link can't be followed.
+type ErrFollowLink struct {
+ Link string
+ UnderlyingError error
+}
+
+func (err *ErrFollowLink) Error() string {
+ return fmt.Sprintf("cannot follow filesystem link %q: %s",
+ err.Link, err.UnderlyingError)
+}
+
+// ErrMakeLink indicates that a protector link can't be created.
+type ErrMakeLink struct {
+ Target *Mount
+ UnderlyingError error
+}
+
+func (err *ErrMakeLink) Error() string {
+ return fmt.Sprintf("cannot create filesystem link to %q: %s",
+ err.Target.Path, err.UnderlyingError)
+}
+
+// ErrNotAMountpoint indicates that a path is not a mountpoint.
+type ErrNotAMountpoint struct {
+ Path string
+}
+
+func (err *ErrNotAMountpoint) Error() string {
+ return fmt.Sprintf("%q is not a mountpoint", err.Path)
+}
+
+// ErrNotSetup indicates that a filesystem is not setup for fscrypt.
+type ErrNotSetup struct {
+ Mount *Mount
+}
+
+func (err *ErrNotSetup) Error() string {
+ return fmt.Sprintf("filesystem %s is not setup for use with fscrypt", err.Mount.Path)
+}
+
+// ErrPolicyNotFound indicates that the policy metadata was not found.
+type ErrPolicyNotFound struct {
+ Descriptor string
+ Mount *Mount
+}
+
+func (err *ErrPolicyNotFound) Error() string {
+ return fmt.Sprintf("policy metadata for %s not found on filesystem %s",
+ err.Descriptor, err.Mount.Path)
+}
+
+// ErrProtectorNotFound indicates that the protector metadata was not found.
+type ErrProtectorNotFound struct {
+ Descriptor string
+ Mount *Mount
+}
+
+func (err *ErrProtectorNotFound) Error() string {
+ return fmt.Sprintf("protector metadata for %s not found on filesystem %s",
+ err.Descriptor, err.Mount.Path)
+}
// SortDescriptorsByLastMtime indicates whether descriptors are sorted by last
// modification time when being listed. This can be set to true to get
@@ -181,9 +248,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)
}
@@ -195,15 +262,45 @@ func (m *Mount) tempMount() (*Mount, error) {
return &Mount{Path: tempDir}, err
}
-// err modifies an error to contain the path of this filesystem.
-func (m *Mount) err(err error) error {
- return errors.Wrapf(err, "filesystem %s", m.Path)
+// ErrEncryptionNotEnabled indicates that encryption is not enabled on the given
+// filesystem.
+type ErrEncryptionNotEnabled struct {
+ Mount *Mount
+}
+
+func (err *ErrEncryptionNotEnabled) Error() string {
+ return fmt.Sprintf("encryption not enabled on filesystem %s (%s).",
+ err.Mount.Path, err.Mount.Device)
+}
+
+// ErrEncryptionNotSupported indicates that encryption is not supported on the
+// given filesystem.
+type ErrEncryptionNotSupported struct {
+ Mount *Mount
+}
+
+func (err *ErrEncryptionNotSupported) Error() string {
+ return fmt.Sprintf("This kernel doesn't support encryption on %s filesystems.",
+ err.Mount.FilesystemType)
+}
+
+// EncryptionSupportError adds filesystem-specific context to the
+// ErrEncryptionNotEnabled and ErrEncryptionNotSupported errors from the
+// metadata package.
+func (m *Mount) EncryptionSupportError(err error) error {
+ switch err {
+ case metadata.ErrEncryptionNotEnabled:
+ return &ErrEncryptionNotEnabled{m}
+ case metadata.ErrEncryptionNotSupported:
+ return &ErrEncryptionNotSupported{m}
+ }
+ return err
}
// CheckSupport returns an error if this filesystem does not support filesystem
// encryption.
func (m *Mount) CheckSupport() error {
- return m.err(metadata.CheckSupport(m.Path))
+ return m.EncryptionSupportError(metadata.CheckSupport(m.Path))
}
// CheckSetup returns an error if all the fscrypt metadata directories do not
@@ -217,7 +314,7 @@ func (m *Mount) CheckSetup() error {
if baseGood && policyGood && protectorGood {
return nil
}
- return m.err(ErrNotSetup)
+ return &ErrNotSetup{m}
}
// makeDirectories creates the three metadata directories with the correct
@@ -244,21 +341,21 @@ func (m *Mount) makeDirectories() error {
// or no files in the baseDir are created.
func (m *Mount) Setup() error {
if m.CheckSetup() == nil {
- return m.err(ErrAlreadySetup)
+ return &ErrAlreadySetup{m}
}
// We build the directories under a temp Mount and then move into place.
temp, err := m.tempMount()
if err != nil {
- return m.err(err)
+ return err
}
defer os.RemoveAll(temp.Path)
if err = temp.makeDirectories(); err != nil {
- return m.err(err)
+ return err
}
// Atomically move directory into place.
- return m.err(os.Rename(temp.BaseDir(), m.BaseDir()))
+ return os.Rename(temp.BaseDir(), m.BaseDir())
}
// RemoveAllMetadata removes all the policy and protector metadata from the
@@ -273,12 +370,12 @@ func (m *Mount) RemoveAllMetadata() error {
// temp will hold the old metadata temporarily
temp, err := m.tempMount()
if err != nil {
- return m.err(err)
+ return err
}
defer os.RemoveAll(temp.Path)
// Move directory into temp (to be destroyed on defer)
- return m.err(os.Rename(m.BaseDir(), temp.BaseDir()))
+ return os.Rename(m.BaseDir(), temp.BaseDir())
}
func syncDirectory(dirPath string) error {
@@ -333,7 +430,7 @@ func (m *Mount) writeDataAtomic(path string, data []byte) error {
// path. This will overwrite any existing data. The operation is atomic.
func (m *Mount) addMetadata(path string, md metadata.Metadata) error {
if err := md.CheckValidity(); err != nil {
- return errors.Wrap(ErrInvalidMetadata, err.Error())
+ return errors.Wrap(err, "provided metadata is invalid")
}
data, err := proto.Marshal(md)
@@ -350,20 +447,16 @@ func (m *Mount) addMetadata(path string, md metadata.Metadata) error {
func (m *Mount) getMetadata(path string, md metadata.Metadata) error {
data, err := ioutil.ReadFile(path)
if err != nil {
- log.Printf("could not read metadata at %q", path)
- if os.IsNotExist(err) {
- return errors.Wrapf(ErrNoMetadata, "descriptor %s", filepath.Base(path))
- }
+ log.Printf("could not read metadata from %q: %v", path, err)
return err
}
if err := proto.Unmarshal(data, md); err != nil {
- return errors.Wrap(ErrCorruptMetadata, err.Error())
+ return &ErrCorruptMetadata{path, err}
}
if err := md.CheckValidity(); err != nil {
- log.Printf("metadata at %q is not valid", path)
- return errors.Wrap(ErrCorruptMetadata, err.Error())
+ return &ErrCorruptMetadata{path, err}
}
log.Printf("successfully read metadata from %q", path)
@@ -374,14 +467,11 @@ func (m *Mount) getMetadata(path string, md metadata.Metadata) error {
// path. Works with regular or linked metadata.
func (m *Mount) removeMetadata(path string) error {
if err := os.Remove(path); err != nil {
- log.Printf("could not remove metadata at %q", path)
- if os.IsNotExist(err) {
- return errors.Wrapf(ErrNoMetadata, "descriptor %s", filepath.Base(path))
- }
+ log.Printf("could not remove metadata file at %q: %v", path, err)
return err
}
- log.Printf("successfully removed metadata at %q", path)
+ log.Printf("successfully removed metadata file at %q", path)
return nil
}
@@ -394,10 +484,11 @@ func (m *Mount) AddProtector(data *metadata.ProtectorData) error {
return err
}
if isRegularFile(m.linkedProtectorPath(data.ProtectorDescriptor)) {
- return m.err(ErrLinkedProtector)
+ return errors.Errorf("cannot modify linked protector %s on filesystem %s",
+ data.ProtectorDescriptor, m.Path)
}
path := m.protectorPath(data.ProtectorDescriptor)
- return m.err(m.addMetadata(path, data))
+ return m.addMetadata(path, data)
}
// AddLinkedProtector adds a link in this filesystem to the protector metadata
@@ -419,10 +510,10 @@ func (m *Mount) AddLinkedProtector(descriptor string, dest *Mount) (bool, error)
if err == nil {
existingLinkedMnt, err := getMountFromLink(string(existingLink))
if err != nil {
- return false, err
+ return false, errors.Wrap(err, linkPath)
}
if existingLinkedMnt != dest {
- return false, errors.Wrapf(ErrFollowLink, "link %q points to %q, but expected %q",
+ return false, errors.Errorf("link %q points to %q, but expected %q",
linkPath, existingLinkedMnt.Path, dest.Path)
}
return false, nil
@@ -435,9 +526,9 @@ func (m *Mount) AddLinkedProtector(descriptor string, dest *Mount) (bool, error)
var newLink string
newLink, err = makeLink(dest, "UUID")
if err != nil {
- return false, dest.err(err)
+ return false, err
}
- return true, m.err(m.writeDataAtomic(linkPath, []byte(newLink)))
+ return true, m.writeDataAtomic(linkPath, []byte(newLink))
}
// GetRegularProtector looks up the protector metadata by descriptor. This will
@@ -448,7 +539,11 @@ func (m *Mount) GetRegularProtector(descriptor string) (*metadata.ProtectorData,
}
data := new(metadata.ProtectorData)
path := m.protectorPath(descriptor)
- return data, m.err(m.getMetadata(path, data))
+ err := m.getMetadata(path, data)
+ if os.IsNotExist(err) {
+ err = &ErrProtectorNotFound{descriptor, m}
+ }
+ return data, err
}
// GetProtector returns the Mount of the filesystem containing the information
@@ -459,24 +554,24 @@ func (m *Mount) GetProtector(descriptor string) (*Mount, *metadata.ProtectorData
return nil, nil, err
}
// Get the link data from the link file
- link, err := ioutil.ReadFile(m.linkedProtectorPath(descriptor))
+ path := m.linkedProtectorPath(descriptor)
+ link, err := ioutil.ReadFile(path)
if err != nil {
// If the link doesn't exist, try for a regular protector.
if os.IsNotExist(err) {
data, err := m.GetRegularProtector(descriptor)
return m, data, err
}
- return nil, nil, m.err(err)
+ return nil, nil, err
}
-
+ log.Printf("following protector link %s", path)
linkedMnt, err := getMountFromLink(string(link))
if err != nil {
- return nil, nil, m.err(err)
+ return nil, nil, errors.Wrap(err, path)
}
data, err := linkedMnt.GetRegularProtector(descriptor)
if err != nil {
- log.Print(err)
- return nil, nil, m.err(errors.Wrapf(ErrLinkExpired, "protector %s", descriptor))
+ return nil, nil, &ErrFollowLink{string(link), err}
}
return linkedMnt, data, nil
}
@@ -490,10 +585,13 @@ func (m *Mount) RemoveProtector(descriptor string) error {
// We first try to remove the linkedProtector. If that metadata does not
// exist, we try to remove the normal protector.
err := m.removeMetadata(m.linkedProtectorPath(descriptor))
- if errors.Cause(err) == ErrNoMetadata {
+ if os.IsNotExist(err) {
err = m.removeMetadata(m.protectorPath(descriptor))
+ if os.IsNotExist(err) {
+ err = &ErrProtectorNotFound{descriptor, m}
+ }
}
- return m.err(err)
+ return err
}
// ListProtectors lists the descriptors of all protectors on this filesystem.
@@ -502,8 +600,7 @@ func (m *Mount) ListProtectors() ([]string, error) {
if err := m.CheckSetup(); err != nil {
return nil, err
}
- protectors, err := m.listDirectory(m.ProtectorDir())
- return protectors, m.err(err)
+ return m.listDirectory(m.ProtectorDir())
}
// AddPolicy adds the policy metadata to the filesystem storage.
@@ -512,7 +609,7 @@ func (m *Mount) AddPolicy(data *metadata.PolicyData) error {
return err
}
- return m.err(m.addMetadata(m.policyPath(data.KeyDescriptor), data))
+ return m.addMetadata(m.PolicyPath(data.KeyDescriptor), data)
}
// GetPolicy looks up the policy metadata by descriptor.
@@ -521,7 +618,11 @@ 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))
+ err := m.getMetadata(m.PolicyPath(descriptor), data)
+ if os.IsNotExist(err) {
+ err = &ErrPolicyNotFound{descriptor, m}
+ }
+ return data, err
}
// RemovePolicy deletes the policy metadata from the filesystem storage.
@@ -529,7 +630,11 @@ func (m *Mount) RemovePolicy(descriptor string) error {
if err := m.CheckSetup(); err != nil {
return err
}
- return m.err(m.removeMetadata(m.policyPath(descriptor)))
+ err := m.removeMetadata(m.PolicyPath(descriptor))
+ if os.IsNotExist(err) {
+ err = &ErrPolicyNotFound{descriptor, m}
+ }
+ return err
}
// ListPolicies lists the descriptors of all policies on this filesystem.
@@ -537,8 +642,7 @@ func (m *Mount) ListPolicies() ([]string, error) {
if err := m.CheckSetup(); err != nil {
return nil, err
}
- policies, err := m.listDirectory(m.PolicyDir())
- return policies, m.err(err)
+ return m.listDirectory(m.PolicyDir())
}
type namesAndTimes struct {
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 4bed96a..9b534bd 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -27,7 +27,6 @@ import (
"testing"
"github.com/golang/protobuf/proto"
- "github.com/pkg/errors"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/metadata"
@@ -367,7 +366,7 @@ func TestLinkedProtector(t *testing.T) {
// Get the protector though the second system
_, err = fakeMnt.GetRegularProtector(protector.ProtectorDescriptor)
- if errors.Cause(err) != ErrNoMetadata {
+ if _, ok := err.(*ErrProtectorNotFound); !ok {
t.Fatal(err)
}
diff --git a/filesystem/mountpoint.go b/filesystem/mountpoint.go
index acddbae..c830780 100644
--- a/filesystem/mountpoint.go
+++ b/filesystem/mountpoint.go
@@ -380,7 +380,7 @@ func FindMount(path string) (*Mount, error) {
func GetMount(mountpoint string) (*Mount, error) {
mnt, err := FindMount(mountpoint)
if err != nil {
- return nil, errors.Wrap(ErrNotAMountpoint, mountpoint)
+ return nil, &ErrNotAMountpoint{mountpoint}
}
// Check whether 'mountpoint' names the same directory as 'mnt.Path'.
// Use os.SameFile() (i.e., compare inode numbers) rather than compare
@@ -394,7 +394,7 @@ func GetMount(mountpoint string) (*Mount, error) {
return nil, err
}
if !os.SameFile(fi1, fi2) {
- return nil, errors.Wrap(ErrNotAMountpoint, mountpoint)
+ return nil, &ErrNotAMountpoint{mountpoint}
}
return mnt, nil
}
@@ -410,22 +410,22 @@ func getMountFromLink(link string) (*Mount, error) {
link = strings.TrimSpace(link)
linkComponents := strings.Split(link, "=")
if len(linkComponents) != 2 {
- return nil, errors.Wrapf(ErrFollowLink, "link %q format is invalid", link)
+ return nil, &ErrFollowLink{link, errors.New("invalid link format")}
}
token := linkComponents[0]
value := linkComponents[1]
if token != uuidToken {
- return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token)
+ return nil, &ErrFollowLink{link, errors.Errorf("token type %q not supported", token)}
}
// See if UUID points to an existing device
searchPath := filepath.Join(uuidDirectory, value)
if filepath.Base(searchPath) != value {
- return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value)
+ return nil, &ErrFollowLink{link, errors.Errorf("invalid UUID format %q", value)}
}
deviceNumber, err := getDeviceNumber(searchPath)
if err != nil {
- return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value)
+ return nil, &ErrFollowLink{link, errors.Errorf("no device with UUID %s", value)}
}
// Lookup mountpoints for device in global store
@@ -436,11 +436,11 @@ func getMountFromLink(link string) (*Mount, error) {
}
mnt, ok := mountsByDevice[deviceNumber]
if !ok {
- return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q (%v)",
- getDeviceName(deviceNumber), deviceNumber)
+ return nil, &ErrFollowLink{link, errors.Errorf("no mounts for device %q (%v)",
+ getDeviceName(deviceNumber), deviceNumber)}
}
if mnt == nil {
- return nil, filesystemLacksMainMountError(deviceNumber)
+ return nil, &ErrFollowLink{link, filesystemLacksMainMountError(deviceNumber)}
}
return mnt, nil
}
@@ -450,12 +450,12 @@ func getMountFromLink(link string) (*Mount, error) {
// error is returned if the mount has no device, or no UUID.
func makeLink(mnt *Mount, token string) (string, error) {
if token != uuidToken {
- return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token)
+ return "", &ErrMakeLink{mnt, errors.Errorf("token type %q not supported", token)}
}
dirContents, err := ioutil.ReadDir(uuidDirectory)
if err != nil {
- return "", errors.Wrap(ErrMakeLink, err.Error())
+ return "", &ErrMakeLink{mnt, err}
}
for _, fileInfo := range dirContents {
if fileInfo.Mode()&os.ModeSymlink == 0 {
@@ -471,6 +471,6 @@ func makeLink(mnt *Mount, token string) (string, error) {
return fmt.Sprintf("%s=%s", uuidToken, uuid), nil
}
}
- return "", errors.Wrapf(ErrMakeLink, "device %q (%v) has no UUID",
- mnt.Device, mnt.DeviceNumber)
+ return "", &ErrMakeLink{mnt, errors.Errorf("cannot determine UUID of device %q (%v)",
+ mnt.Device, mnt.DeviceNumber)}
}
diff --git a/keyring/fs_keyring.go b/keyring/fs_keyring.go
index 262e0e5..9b949b9 100644
--- a/keyring/fs_keyring.go
+++ b/keyring/fs_keyring.go
@@ -203,7 +203,9 @@ func fsAddEncryptionKey(key *crypto.Key, descriptor string,
log.Printf("FS_IOC_ADD_ENCRYPTION_KEY(%q, %s, <raw>) = %v", mount.Path, descriptor, errno)
if errno != 0 {
- return errors.Wrap(ErrKeyAdd, errno.Error())
+ return errors.Wrapf(errno,
+ "error adding key with descriptor %s to filesystem %s",
+ descriptor, mount.Path)
}
if descriptor, err = validateKeyDescriptor(&arg.Key_spec, descriptor); err != nil {
fsRemoveEncryptionKey(descriptor, mount, user)
@@ -266,7 +268,9 @@ func fsRemoveEncryptionKey(descriptor string, mount *filesystem.Mount,
}
return ErrKeyNotPresent
default:
- return errors.Wrap(ErrKeyRemove, errno.Error())
+ return errors.Wrapf(errno,
+ "error removing key with descriptor %s from filesystem %s",
+ descriptor, mount.Path)
}
}
@@ -298,7 +302,10 @@ func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount,
log.Printf("FS_IOC_GET_ENCRYPTION_KEY_STATUS(%q, %s) = %v, status=%d, status_flags=0x%x",
mount.Path, descriptor, errno, arg.Status, arg.Status_flags)
if errno != 0 {
- return KeyStatusUnknown, errors.Wrap(ErrKeySearch, errno.Error())
+ return KeyStatusUnknown,
+ errors.Wrapf(errno,
+ "error getting status of key with descriptor %s on filesystem %s",
+ descriptor, mount.Path)
}
switch arg.Status {
case unix.FSCRYPT_KEY_STATUS_ABSENT:
@@ -313,6 +320,7 @@ func fsGetEncryptionKeyStatus(descriptor string, mount *filesystem.Mount,
return KeyAbsentButFilesBusy, nil
default:
return KeyStatusUnknown,
- errors.Wrapf(ErrKeySearch, "unknown key status (%d)", arg.Status)
+ errors.Errorf("unknown key status (%d) for key with descriptor %s on filesystem %s",
+ arg.Status, descriptor, mount.Path)
}
}
diff --git a/keyring/keyring.go b/keyring/keyring.go
index fb9cc0e..5ddceaf 100644
--- a/keyring/keyring.go
+++ b/keyring/keyring.go
@@ -43,15 +43,9 @@ 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")
+ ErrKeyFilesOpen = errors.New("some files using the key are still open")
+ ErrKeyNotPresent = errors.New("key not present or already removed")
ErrV2PoliciesUnsupported = errors.New("kernel is too old to support v2 encryption policies")
)
diff --git a/keyring/user_keyring.go b/keyring/user_keyring.go
index beeb36d..0ea4689 100644
--- a/keyring/user_keyring.go
+++ b/keyring/user_keyring.go
@@ -36,6 +36,29 @@ import (
"github.com/google/fscrypt/util"
)
+// ErrAccessUserKeyring indicates that a user's keyring cannot be
+// accessed.
+type ErrAccessUserKeyring struct {
+ TargetUser *user.User
+ UnderlyingError error
+}
+
+func (err *ErrAccessUserKeyring) Error() string {
+ return fmt.Sprintf("could not access user keyring for %q: %s",
+ err.TargetUser.Username, err.UnderlyingError)
+}
+
+// ErrSessionUserKeyring indicates that a user's keyring is not linked
+// into the session keyring.
+type ErrSessionUserKeyring struct {
+ TargetUser *user.User
+}
+
+func (err *ErrSessionUserKeyring) Error() string {
+ return fmt.Sprintf("user keyring for %q is not linked into the session keyring",
+ err.TargetUser.Username)
+}
+
// KeyType is always logon as required by filesystem encryption.
const KeyType = "logon"
@@ -67,7 +90,9 @@ func userAddKey(key *crypto.Key, description string, targetUser *user.User) erro
log.Printf("KeyctlAddKey(%s, %s, <data>, %d) = %d, %v",
KeyType, description, keyringID, keyID, err)
if err != nil {
- return errors.Wrap(ErrKeyAdd, err.Error())
+ return errors.Wrapf(err,
+ "error adding key with description %s to user keyring for %q",
+ description, targetUser.Username)
}
return nil
}
@@ -86,7 +111,9 @@ func userRemoveKey(description string, targetUser *user.User) error {
_, err = unix.KeyctlInt(unix.KEYCTL_UNLINK, keyID, keyringID, 0, 0)
log.Printf("KeyctlUnlink(%d, %d) = %v", keyID, keyringID, err)
if err != nil {
- return errors.Wrap(ErrKeyRemove, err.Error())
+ return errors.Wrapf(err,
+ "error removing key with description %s from user keyring for %q",
+ description, targetUser.Username)
}
return nil
}
@@ -106,7 +133,9 @@ func userFindKey(description string, targetUser *user.User) (int, int, error) {
keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0)
log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err)
if err != nil {
- return 0, 0, errors.Wrap(ErrKeySearch, err.Error())
+ return 0, 0, errors.Wrapf(err,
+ "error searching for key %s in user keyring for %q",
+ description, targetUser.Username)
}
return keyID, keyringID, err
}
@@ -123,14 +152,14 @@ func UserKeyringID(targetUser *user.User, checkSession bool) (int, error) {
uid := util.AtoiOrPanic(targetUser.Uid)
targetKeyring, err := userKeyringIDLookup(uid)
if err != nil {
- return 0, errors.Wrap(ErrAccessUserKeyring, err.Error())
+ return 0, &ErrAccessUserKeyring{targetUser, err}
}
if !util.IsUserRoot() {
// Make sure the returned keyring will be accessible by checking
// that it is in the session keyring.
if checkSession && !isUserKeyringInSession(uid) {
- return 0, ErrSessionUserKeying
+ return 0, &ErrSessionUserKeyring{targetUser}
}
return targetKeyring, nil
}
@@ -139,12 +168,14 @@ func UserKeyringID(targetUser *user.User, checkSession bool) (int, error) {
// the root user's user keyring (which will not be garbage collected).
rootKeyring, err := userKeyringIDLookup(0)
if err != nil {
- return 0, errors.Wrap(ErrLinkUserKeyring, err.Error())
+ return 0, errors.Wrapf(err, "error looking up root's user keyring")
}
if rootKeyring != targetKeyring {
if err = keyringLink(targetKeyring, rootKeyring); err != nil {
- return 0, errors.Wrap(ErrLinkUserKeyring, err.Error())
+ return 0, errors.Wrapf(err,
+ "error linking user keyring for %q into root's user keyring",
+ targetUser.Username)
}
}
return targetKeyring, nil
diff --git a/metadata/policy.go b/metadata/policy.go
index b95bf42..76c2e6f 100644
--- a/metadata/policy.go
+++ b/metadata/policy.go
@@ -22,9 +22,12 @@ package metadata
import (
"encoding/hex"
+ "fmt"
"log"
"math"
"os"
+ "os/user"
+ "strconv"
"unsafe"
"github.com/pkg/errors"
@@ -33,38 +36,70 @@ import (
"github.com/google/fscrypt/util"
)
-// Encryption specific errors
var (
+ // ErrEncryptionNotSupported indicates that encryption is not supported
+ // on the given filesystem, and there is no way to enable it.
ErrEncryptionNotSupported = errors.New("encryption not supported")
- ErrEncryptionNotEnabled = errors.New("encryption not enabled")
- ErrNotEncrypted = errors.New("file or directory not encrypted")
- ErrEncrypted = errors.New("file or directory already encrypted")
- ErrBadEncryptionOptions = util.SystemError("invalid encryption options provided")
+
+ // ErrEncryptionNotEnabled indicates that encryption is not supported on
+ // the given filesystem, but there is a way to enable it.
+ ErrEncryptionNotEnabled = errors.New("encryption not enabled")
)
-// policyIoctl is a wrapper around the ioctls that get and set encryption
-// policies: FS_IOC_GET_ENCRYPTION_POLICY, FS_IOC_GET_ENCRYPTION_POLICY_EX, and
-// FS_IOC_SET_ENCRYPTION_POLICY. It translates the raw errno values into more
-// descriptive errors.
+// ErrAlreadyEncrypted indicates that the path is already encrypted.
+type ErrAlreadyEncrypted struct {
+ Path string
+}
+
+func (err *ErrAlreadyEncrypted) Error() string {
+ return fmt.Sprintf("file or directory %q is already encrypted", err.Path)
+}
+
+// ErrBadEncryptionOptions indicates that unsupported encryption options were given.
+type ErrBadEncryptionOptions struct {
+ Path string
+ Options *EncryptionOptions
+}
+
+func (err *ErrBadEncryptionOptions) Error() string {
+ return fmt.Sprintf(`cannot encrypt %q because the kernel doesn't support the requested encryption options.
+
+ The options are %s`, err.Path, err.Options)
+}
+
+// ErrDirectoryNotOwned indicates a directory can't be encrypted because it's
+// owned by another user.
+type ErrDirectoryNotOwned struct {
+ Path string
+ Owner uint32
+}
+
+func (err *ErrDirectoryNotOwned) Error() string {
+ owner := strconv.Itoa(int(err.Owner))
+ if u, e := user.LookupId(owner); e == nil && u.Username != "" {
+ owner = u.Username
+ }
+ return fmt.Sprintf(`cannot encrypt %q because it's owned by another user (%s).
+
+ Encryption can only be enabled on a directory you own, even if you have
+ write access to the directory.`, err.Path, owner)
+}
+
+// ErrNotEncrypted indicates that the path is not encrypted.
+type ErrNotEncrypted struct {
+ Path string
+}
+
+func (err *ErrNotEncrypted) Error() string {
+ return fmt.Sprintf("file or directory %q is not encrypted", err.Path)
+}
+
func policyIoctl(file *os.File, request uintptr, arg unsafe.Pointer) error {
- // The returned errno value can sometimes give strange errors, so we
- // return encryption specific errors.
_, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(arg))
- switch errno {
- case 0:
+ if errno == 0 {
return nil
- case unix.ENOTTY:
- return ErrEncryptionNotSupported
- case unix.EOPNOTSUPP:
- return ErrEncryptionNotEnabled
- case unix.ENODATA, unix.ENOENT:
- // ENOENT was returned instead of ENODATA on some filesystems before v4.11.
- return ErrNotEncrypted
- case unix.EEXIST:
- return ErrEncrypted
- default:
- return errno
}
+ return errno
}
// Maps EncryptionOptions.Padding <-> FSCRYPT_POLICY_FLAGS
@@ -125,13 +160,23 @@ func GetPolicy(path string) (*PolicyData, error) {
arg.Size = uint64(unsafe.Sizeof(arg.Policy))
policyPtr := util.Ptr(arg.Policy[:])
err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY_EX, unsafe.Pointer(&arg))
- if err == ErrEncryptionNotSupported {
+ if err == unix.ENOTTY {
// Fall back to the old version of the ioctl. This works for v1 policies only.
err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, policyPtr)
arg.Size = uint64(unsafe.Sizeof(unix.FscryptPolicyV1{}))
}
- if err != nil {
- return nil, errors.Wrapf(err, "get encryption policy %s", path)
+ switch err {
+ case nil:
+ break
+ case unix.ENOTTY:
+ return nil, ErrEncryptionNotSupported
+ case unix.EOPNOTSUPP:
+ return nil, ErrEncryptionNotEnabled
+ case unix.ENODATA, unix.ENOENT:
+ // ENOENT was returned instead of ENODATA on some filesystems before v4.11.
+ return nil, &ErrNotEncrypted{path}
+ default:
+ return nil, errors.Wrapf(err, "failed to get encryption policy of %q", path)
}
switch arg.Policy[0] { // arg.policy.version
case unix.FSCRYPT_POLICY_V1:
@@ -237,7 +282,6 @@ func SetPolicy(path string, data *PolicyData) error {
default:
err = errors.Errorf("policy version of %d is invalid", data.Options.PolicyVersion)
}
-
if err == unix.EINVAL {
// Before kernel v4.11, many different errors all caused unix.EINVAL to be returned.
// We try to disambiguate this error here. This disambiguation will not always give
@@ -247,14 +291,27 @@ func SetPolicy(path string, data *PolicyData) error {
err = unix.ENOTDIR
} else if _, policyErr := GetPolicy(path); policyErr == nil {
// Checking if a policy is already set on this directory
- err = ErrEncrypted
- } else {
- // Default to generic "bad options".
- err = ErrBadEncryptionOptions
+ err = unix.EEXIST
}
}
-
- return errors.Wrapf(err, "set encryption policy %s", path)
+ switch err {
+ case nil:
+ return nil
+ case unix.EACCES:
+ var stat unix.Stat_t
+ if statErr := unix.Stat(path, &stat); statErr == nil && stat.Uid != uint32(os.Geteuid()) {
+ return &ErrDirectoryNotOwned{path, stat.Uid}
+ }
+ case unix.EEXIST:
+ return &ErrAlreadyEncrypted{path}
+ case unix.EINVAL:
+ return &ErrBadEncryptionOptions{path, data.Options}
+ case unix.ENOTTY:
+ return ErrEncryptionNotSupported
+ case unix.EOPNOTSUPP:
+ return ErrEncryptionNotEnabled
+ }
+ return errors.Wrapf(err, "failed to set encryption policy on %q", path)
}
// CheckSupport returns an error if the filesystem containing path does not
@@ -282,6 +339,10 @@ func CheckSupport(path string) error {
Please open an issue, filesystem %q may be corrupted.`, path)
case unix.EINVAL, unix.EACCES:
return nil
+ case unix.ENOTTY:
+ return ErrEncryptionNotSupported
+ case unix.EOPNOTSUPP:
+ return ErrEncryptionNotEnabled
}
- return err
+ return errors.Wrapf(err, "unexpected error checking for encryption support on filesystem %q", path)
}