aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt/errors.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/fscrypt/errors.go')
-rw-r--r--cmd/fscrypt/errors.go267
1 files changed, 210 insertions, 57 deletions
diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go
index 81a6798..e4da884 100644
--- a/cmd/fscrypt/errors.go
+++ b/cmd/fscrypt/errors.go
@@ -30,12 +30,13 @@ import (
"github.com/pkg/errors"
"github.com/urfave/cli"
+ "golang.org/x/sys/unix"
"github.com/google/fscrypt/actions"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/filesystem"
+ "github.com/google/fscrypt/keyring"
"github.com/google/fscrypt/metadata"
- "github.com/google/fscrypt/security"
"github.com/google/fscrypt/util"
)
@@ -45,8 +46,7 @@ const failureExitCode = 1
// Various errors used for the top level user interface
var (
ErrCanceled = errors.New("operation canceled")
- ErrNoDesctructiveOps = errors.New("operation would be destructive")
- ErrMaxPassphrase = util.SystemError("max passphrase length exceeded")
+ ErrNoDestructiveOps = errors.New("operation would be destructive")
ErrInvalidSource = errors.New("invalid source type")
ErrPassphraseMismatch = errors.New("entered passphrases do not match")
ErrSpecifyProtector = errors.New("multiple protectors available")
@@ -55,15 +55,47 @@ 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")
- 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")
ErrSpecifyUser = errors.New("user must be specified when run as root")
+ 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 `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.
@@ -74,67 +106,188 @@ func getFullName(c *cli.Context) string {
return c.App.HelpName
}
+func isGrubInstalledOnFilesystem(mnt *filesystem.Mount) bool {
+ dir := filepath.Join(mnt.Path, "boot/grub")
+ grubDirMount, _ := filesystem.FindMount(dir)
+ return grubDirMount == mnt
+}
+
+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 := os.Getpagesize()
+ if int64(statfs.Bsize) != int64(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 isGrubInstalledOnFilesystem(mnt) {
+ 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 := filepath.Clean(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 %q
+ > fscrypt encrypt %q
+ > cp -a -T %q %q
+ > find %q -type f -print0 | xargs -0 shred -n1 --remove=unlink
+ > rm -rf %q
+ > mv %q %q
+
+ 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."
+ }
+ case "ceph":
+ if !util.IsKernelVersionAtLeast(6, 6) {
+ return "CephFS encryption requires kernel v6.6 or later."
+ }
+ }
+ return ""
+ case *filesystem.ErrNoCreatePermission:
+ return `For how to allow users to create fscrypt metadata on a
+ filesystem, refer to
+ https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem`
+ 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".`
+ case *metadata.ErrLockedRegularFile:
+ return `It is not possible to operate directly on a locked
+ regular file, since the kernel does not support this.
+ Specify the parent directory instead. (For loose files,
+ any directory with the file's policy works.)`
+ }
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 security.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 security.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 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 ErrNoDesctructiveOps:
- return fmt.Sprintf("Use %s to automatically run destructive operations.", shortDisplay(forceFlag))
+ 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 ErrNoDestructiveOps:
+ 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))
+ return fmt.Sprintf("Use %s or %s to specify a protector.",
+ shortDisplay(protectorFlag), shortDisplay(unlockWithFlag))
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
%s=false (this may leave encrypted files and directories
in an accessible state).`, shortDisplay(dropCachesFlag))
+ case ErrFsKeyringPerm:
+ return `Either this command should be run as root, or you should
+ set '"use_fs_keyring_for_v1_policies": false' in
+ /etc/fscrypt.conf, or you should 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).`
case ErrSpecifyUser:
return fmt.Sprintf(`When running this command as root, you
usually still want to provision/remove keys for a normal
@@ -151,12 +304,12 @@ func getErrorSuggestions(err error) string {
}
// newExitError creates a new error for a given context and normal error. The
-// returned error prepends the name of the relevant command and will make
-// fscrypt return a non-zero exit value.
+// returned error prepends an error tag and the name of the relevant command,
+// and it will make fscrypt return a non-zero exit value.
func newExitError(c *cli.Context, err error) error {
- // Prepend the full name and append suggestions (if any)
- fullNamePrefix := getFullName(c) + ": "
- message := fullNamePrefix + wrapText(err.Error(), utf8.RuneCountInString(fullNamePrefix))
+ // Prepend the error tag and full name, and append suggestions (if any)
+ prefix := "[ERROR] " + getFullName(c) + ": "
+ message := prefix + wrapText(err.Error(), utf8.RuneCountInString(prefix))
if suggestion := getErrorSuggestions(err); suggestion != "" {
message += "\n\n" + wrapText(suggestion, 0)
@@ -165,8 +318,8 @@ func newExitError(c *cli.Context, err error) error {
return cli.NewExitError(message, failureExitCode)
}
-// usageError implements cli.ExitCoder to will print the usage and the return a
-// non-zero value. This error should be used when a command is used incorrectly.
+// usageError implements cli.ExitCoder to print the usage and return a non-zero
+// value. This error should be used when a command is used incorrectly.
type usageError struct {
c *cli.Context
message string