aboutsummaryrefslogtreecommitdiff
path: root/metadata
diff options
context:
space:
mode:
Diffstat (limited to 'metadata')
-rw-r--r--metadata/checks.go193
-rw-r--r--metadata/policy.go111
-rw-r--r--metadata/policy_test.go13
3 files changed, 173 insertions, 144 deletions
diff --git a/metadata/checks.go b/metadata/checks.go
index 5d0ce59..074d79e 100644
--- a/metadata/checks.go
+++ b/metadata/checks.go
@@ -20,178 +20,177 @@
package metadata
import (
- "log"
-
"github.com/golang/protobuf/proto"
+ "github.com/pkg/errors"
"fscrypt/util"
)
+var errNotInitialized = errors.New("not initialized")
+
// Metadata is the interface to all of the protobuf structures that can be
-// checked with the IsValid method.
+// checked for validity.
type Metadata interface {
- IsValid() bool
+ CheckValidity() error
proto.Message
}
-// checkValidLength returns true if expected == actual, otherwise it logs an
-// InvalidLengthError.
-func checkValidLength(name string, expected int, actual int) bool {
- if expected != actual {
- log.Print(util.InvalidLengthError(name, expected, actual))
- return false
+// CheckValidity ensures the mode has a name and isn't empty.
+func (m EncryptionOptions_Mode) CheckValidity() error {
+ if m == EncryptionOptions_default {
+ return errNotInitialized
}
- return true
-}
-
-// IsValid ensures the mode has a name and isn't empty.
-func (m EncryptionOptions_Mode) IsValid() bool {
if m.String() == "" {
- log.Print("Encryption mode cannot be the empty string")
- return false
- }
- if m == EncryptionOptions_default {
- log.Print("Encryption mode must be set to a non-default value")
- return false
+ return errors.Errorf("unknown %d", m)
}
- return true
+ return nil
}
-// IsValid ensures the source has a name and isn't empty.
-func (s SourceType) IsValid() bool {
- if s.String() == "" {
- log.Print("SourceType cannot be the empty string")
- return false
- }
+// CheckValidity ensures the source has a name and isn't empty.
+func (s SourceType) CheckValidity() error {
if s == SourceType_default {
- log.Print("SourceType must be set to a non-default value")
- return false
+ return errNotInitialized
+ }
+ if s.String() == "" {
+ return errors.Errorf("unknown %d", s)
}
- return true
+ return nil
}
-// IsValid ensures the hash costs will be accepted by Argon2.
-func (h *HashingCosts) IsValid() bool {
+// CheckValidity ensures the hash costs will be accepted by Argon2.
+func (h *HashingCosts) CheckValidity() error {
if h == nil {
- log.Print("HashingCosts not initialized")
- return false
+ return errNotInitialized
}
- if h.Time == 0 {
- log.Print("Hashing time cost not initialized")
- return false
+ if h.Time <= 0 {
+ return errors.Errorf("time=%d is not positive", h.Time)
}
- if h.Parallelism == 0 {
- log.Print("Hashing parallelism cost not initialized")
- return false
+ if h.Parallelism <= 0 {
+ return errors.Errorf("parallelism=%d is not positive", h.Parallelism)
}
minMemory := 8 * h.Parallelism
if h.Memory < minMemory {
- log.Printf("Hashing memory cost must be at least %d", minMemory)
- return false
+ return errors.Errorf("memory=%d is less than minimum (%d)", h.Memory, minMemory)
}
- return true
+ return nil
}
-// IsValid ensures our buffers are the correct length (or just exist).
-func (w *WrappedKeyData) IsValid() bool {
+// CheckValidity ensures our buffers are the correct length.
+func (w *WrappedKeyData) CheckValidity() error {
if w == nil {
- log.Print("WrappedKeyData not initialized")
- return false
+ return errNotInitialized
}
if len(w.EncryptedKey) == 0 {
- log.Print("EncryptedKey not initialized")
- return false
+ return errors.Wrap(errNotInitialized, "encrypted key")
+ }
+ if err := util.CheckValidLength(IVLen, len(w.IV)); err != nil {
+ return errors.Wrap(err, "IV")
}
- return checkValidLength("IV", IVLen, len(w.IV)) &&
- checkValidLength("HMAC", HMACLen, len(w.Hmac))
+ return errors.Wrap(util.CheckValidLength(HMACLen, len(w.Hmac)), "HMAC")
}
-// IsValid ensures our ProtectorData has the correct fields for its source.
-func (p *ProtectorData) IsValid() bool {
+// CheckValidity ensures our ProtectorData has the correct fields for its source.
+func (p *ProtectorData) CheckValidity() error {
if p == nil {
- log.Print("ProtectorData not initialized")
- return false
+ return errNotInitialized
+ }
+
+ if err := p.Source.CheckValidity(); err != nil {
+ return errors.Wrap(err, "protector source")
}
// Source specific checks
switch p.Source {
case SourceType_pam_passphrase:
if p.Uid < 0 {
- log.Print("The UID should never be negative")
- return false
+ return errors.Errorf("UID=%d is negative", p.Uid)
}
fallthrough
case SourceType_custom_passphrase:
- if !p.Costs.IsValid() || !checkValidLength("Salt", SaltLen, len(p.Salt)) {
- return false
+ if err := p.Costs.CheckValidity(); err != nil {
+ return errors.Wrap(err, "passphrase hashing costs")
+ }
+ if err := util.CheckValidLength(SaltLen, len(p.Salt)); err != nil {
+ return errors.Wrap(err, "passphrase hashing salt")
}
}
// Generic checks
- return p.Source.IsValid() &&
- p.WrappedKey.IsValid() &&
- checkValidLength("EncryptedKey", InternalKeyLen, len(p.WrappedKey.EncryptedKey)) &&
- checkValidLength("ProtectorDescriptor", DescriptorLen, len(p.ProtectorDescriptor))
+ if err := p.WrappedKey.CheckValidity(); err != nil {
+ return errors.Wrap(err, "wrapped protector key")
+ }
+ if err := util.CheckValidLength(DescriptorLen, len(p.ProtectorDescriptor)); err != nil {
+ return errors.Wrap(err, "protector descriptor")
+ }
+ err := util.CheckValidLength(InternalKeyLen, len(p.WrappedKey.EncryptedKey))
+ return errors.Wrap(err, "encrypted protector key")
}
-// IsValid ensures each of the options is valid.
-func (e *EncryptionOptions) IsValid() bool {
+// CheckValidity ensures each of the options is valid.
+func (e *EncryptionOptions) CheckValidity() error {
if e == nil {
- log.Print("EncryptionOptions not initialized")
- return false
+ return errNotInitialized
}
if _, ok := util.Index(e.Padding, paddingArray); !ok {
- log.Printf("Padding of %d is invalid", e.Padding)
- return false
+ return errors.Errorf("padding of %d is invalid", e.Padding)
}
-
- return e.Contents.IsValid() && e.Filenames.IsValid()
+ if err := e.Contents.CheckValidity(); err != nil {
+ return errors.Wrap(err, "contents encryption mode")
+ }
+ return errors.Wrap(e.Filenames.CheckValidity(), "filenames encryption mode")
}
-// IsValid ensures the fields are valid and have the correct lengths.
-func (w *WrappedPolicyKey) IsValid() bool {
+// CheckValidity ensures the fields are valid and have the correct lengths.
+func (w *WrappedPolicyKey) CheckValidity() error {
if w == nil {
- log.Print("WrappedPolicyKey not initialized")
- return false
+ return errNotInitialized
}
- return w.WrappedKey.IsValid() &&
- checkValidLength("EncryptedKey", PolicyKeyLen, len(w.WrappedKey.EncryptedKey)) &&
- checkValidLength("ProtectorDescriptor", DescriptorLen, len(w.ProtectorDescriptor))
+ if err := w.WrappedKey.CheckValidity(); err != nil {
+ return errors.Wrap(err, "wrapped key")
+ }
+ if err := util.CheckValidLength(PolicyKeyLen, len(w.WrappedKey.EncryptedKey)); err != nil {
+ return errors.Wrap(err, "encrypted key")
+ }
+ err := util.CheckValidLength(DescriptorLen, len(w.ProtectorDescriptor))
+ return errors.Wrap(err, "wrapping protector descriptor")
}
-// IsValid ensures the fields and each wrapped key are valid.
-func (p *PolicyData) IsValid() bool {
+// CheckValidity ensures the fields and each wrapped key are valid.
+func (p *PolicyData) CheckValidity() error {
if p == nil {
- log.Print("PolicyData not initialized")
- return false
+ return errNotInitialized
}
// Check each wrapped key
- for _, w := range p.WrappedPolicyKeys {
- if !w.IsValid() {
- return false
+ for i, w := range p.WrappedPolicyKeys {
+ if err := w.CheckValidity(); err != nil {
+ return errors.Wrapf(err, "policy key slot %d", i)
}
}
- return p.Options.IsValid() &&
- checkValidLength("KeyDescriptor", DescriptorLen, len(p.KeyDescriptor))
+ if err := util.CheckValidLength(DescriptorLen, len(p.KeyDescriptor)); err != nil {
+ return errors.Wrap(err, "policy key descriptor")
+ }
+
+ return errors.Wrap(p.Options.CheckValidity(), "policy options")
}
-// IsValid ensures the Config has all the necessary info for its Source.
-func (c *Config) IsValid() bool {
+// CheckValidity ensures the Config has all the necessary info for its Source.
+func (c *Config) CheckValidity() error {
// General checks
if c == nil {
- log.Print("Config not initialized")
- return false
+ return errNotInitialized
}
- if !c.Source.IsValid() || !c.Options.IsValid() {
- return false
+ if err := c.Source.CheckValidity(); err != nil {
+ return errors.Wrap(err, "default config source")
}
// Source specific checks
switch c.Source {
case SourceType_pam_passphrase, SourceType_custom_passphrase:
- return c.HashCosts.IsValid()
- default:
- return true
+ if err := c.HashCosts.CheckValidity(); err != nil {
+ return errors.Wrap(err, "config hashing costs")
+ }
}
+
+ return errors.Wrap(c.Options.CheckValidity(), "config options")
}
diff --git a/metadata/policy.go b/metadata/policy.go
index ac2fde7..259fe04 100644
--- a/metadata/policy.go
+++ b/metadata/policy.go
@@ -22,12 +22,12 @@ package metadata
import (
"encoding/hex"
- "errors"
- "fmt"
"log"
+ "math"
"os"
"unsafe"
+ "github.com/pkg/errors"
"golang.org/x/sys/unix"
"fscrypt/util"
@@ -35,25 +35,18 @@ import (
// Encryption specific errors
var (
- ErrEncryptionNotSupported = errors.New("filesystem encryption not supported")
- ErrEncryptionDisabled = errors.New("filesystem encryption disabled in the kernel config")
+ 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")
)
-// policyIoctl is a wrapper for the ioctl syscall. If opens the file at the path
-// and passes the correct pointers and file descriptors to the IOCTL syscall.
-// This function also takes some of the unclear errors returned by the syscall
-// and translates then into more specific error strings.
-func policyIoctl(path string, request uintptr, policy *unix.FscryptPolicy) error {
- file, err := os.Open(path)
- if err != nil {
- // For PathErrors, we just want the underlying error
- return util.UnderlyingError(err)
- }
- defer file.Close()
-
+// policyIoctl is a wrapper for the ioctl syscall. It passes the correct
+// pointers and file descriptors to the IOCTL syscall. This function also takes
+// some of the unclear errors returned by the syscall and translates then into
+// more specific error strings.
+func policyIoctl(file *os.File, request uintptr, policy *unix.FscryptPolicy) 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(unsafe.Pointer(policy)))
@@ -63,7 +56,7 @@ func policyIoctl(path string, request uintptr, policy *unix.FscryptPolicy) error
case unix.ENOTTY:
return ErrEncryptionNotSupported
case unix.EOPNOTSUPP:
- return ErrEncryptionDisabled
+ return ErrEncryptionNotEnabled
case unix.ENODATA, unix.ENOENT:
// ENOENT was returned instead of ENODATA on some filesystems before v4.11.
return ErrNotEncrypted
@@ -86,10 +79,16 @@ var (
// the KeyDescriptor and the encryption options). Returns an error if the
// path is not encrypted or the policy couldn't be retrieved.
func GetPolicy(path string) (*PolicyData, error) {
- var policy unix.FscryptPolicy
- if err := policyIoctl(path, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil {
+ file, err := os.Open(path)
+ if err != nil {
return nil, err
}
+ defer file.Close()
+
+ var policy unix.FscryptPolicy
+ if err := policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil {
+ return nil, errors.Wrapf(err, "get encryption policy %s", path)
+ }
// Convert the padding flag into an amount of padding
paddingFlag := int64(policy.Flags & unix.FS_POLICY_FLAGS_PAD_MASK)
@@ -97,8 +96,7 @@ func GetPolicy(path string) (*PolicyData, error) {
// This lookup should always succeed
padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray)
if !ok {
- log.Printf("padding flag of %x not found", paddingFlag)
- util.NeverError(util.SystemError("invalid padding flag"))
+ log.Panicf("padding flag of %x not found", paddingFlag)
}
return &PolicyData{
@@ -115,22 +113,25 @@ func GetPolicy(path string) (*PolicyData, error) {
// policy. Returns an error if we cannot set the policy for any reason (not a
// directory, invalid options or KeyDescriptor, etc).
func SetPolicy(path string, data *PolicyData) error {
- // Convert the padding value to a flag
- paddingFlag, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray)
- if !ok {
- return util.InvalidInput(fmt.Sprintf("padding of %d", data.Options.Padding))
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ if err := data.CheckValidity(); err != nil {
+ return errors.Wrap(err, "invalid policy")
}
- // Convert the policyDescriptor to a byte array
- if len(data.KeyDescriptor) != DescriptorLen {
- return util.InvalidLengthError(
- "policy descriptor", DescriptorLen, len(data.KeyDescriptor))
+ // This lookup should always succeed (as policy is valid)
+ paddingFlag, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray)
+ if !ok {
+ log.Panicf("padding of %d was not found", data.Options.Padding)
}
descriptorBytes, err := hex.DecodeString(data.KeyDescriptor)
if err != nil {
- return util.InvalidInput(
- fmt.Sprintf("policy descriptor of %s: %v", data.KeyDescriptor, err))
+ return errors.New("invalid descriptor: " + data.KeyDescriptor)
}
policy := unix.FscryptPolicy{
@@ -141,24 +142,50 @@ func SetPolicy(path string, data *PolicyData) error {
}
copy(policy.Master_key_descriptor[:], descriptorBytes)
- if err = policyIoctl(path, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); err != nil {
+ if err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); 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
// the correct error due to a potential race condition on path.
- if err == unix.EINVAL {
+ if info, statErr := os.Stat(path); statErr != nil || !info.IsDir() {
// Checking if the path is not a directory
- if info, err := os.Stat(path); err != nil || !info.IsDir() {
- return unix.ENOTDIR
- }
+ err = unix.ENOTDIR
+ } else if _, policyErr := GetPolicy(path); policyErr == nil {
// Checking if a policy is already set on this directory
- if _, err := GetPolicy(path); err == nil {
- return ErrEncrypted
- }
- // Could not get a more detailed error, return generic "bad options".
- return ErrBadEncryptionOptions
+ err = ErrEncrypted
+ } else {
+ // Default to generic "bad options".
+ err = ErrBadEncryptionOptions
}
+ }
+
+ return errors.Wrapf(err, "set encryption policy %s", path)
+}
+
+// CheckSupport returns an error if the filesystem containing path does not
+// support filesystem encryption. This can be for many reasons including an
+// incompatible kernel or filesystem or not enabling the right feature flags.
+func CheckSupport(path string) error {
+ file, err := os.Open(path)
+ if err != nil {
return err
}
+ defer file.Close()
+
+ // On supported directories, giving a bad policy will return EINVAL
+ badPolicy := unix.FscryptPolicy{
+ Version: math.MaxUint8,
+ Contents_encryption_mode: math.MaxUint8,
+ Filenames_encryption_mode: math.MaxUint8,
+ Flags: math.MaxUint8,
+ }
- return nil
+ err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &badPolicy)
+ switch err {
+ case nil:
+ log.Panicf(`FS_IOC_SET_ENCRYPTION_POLICY succeeded when it should have failed.
+ Please open an issue, filesystem %q may be corrupted.`, path)
+ case unix.EINVAL, unix.EACCES:
+ return nil
+ }
+ return err
}
diff --git a/metadata/policy_test.go b/metadata/policy_test.go
index 6dc2567..58e19d7 100644
--- a/metadata/policy_test.go
+++ b/metadata/policy_test.go
@@ -25,6 +25,8 @@ import (
"path/filepath"
"reflect"
"testing"
+
+ . "fscrypt/util"
)
const goodDescriptor = "0123456789abcdef"
@@ -34,13 +36,14 @@ var goodPolicy = &PolicyData{
Options: DefaultOptions,
}
-// Creates a temporary directory in TEST_FILESYSTEM_ROOT for testing. Fails if
-// the root directory is not specified.
+// Creates a temporary directory for testing.
func createTestDirectory() (directory string, err error) {
- baseDirectory := os.Getenv("TEST_FILESYSTEM_ROOT")
+ baseDirectory, err := TestPath()
+ if err != nil {
+ return
+ }
if s, err := os.Stat(baseDirectory); err != nil || !s.IsDir() {
- return "", fmt.Errorf("invalid directory %q: "+
- "set TEST_FILESYSTEM_ROOT to be a valid directory", baseDirectory)
+ return "", fmt.Errorf("%s: %q is not a valid directory", TestEnvVarName, baseDirectory)
}
directoryPath := filepath.Join(baseDirectory, "test")