diff options
Diffstat (limited to 'metadata/policy.go')
| -rw-r--r-- | metadata/policy.go | 170 |
1 files changed, 125 insertions, 45 deletions
diff --git a/metadata/policy.go b/metadata/policy.go index f9af44a..b95bf42 100644 --- a/metadata/policy.go +++ b/metadata/policy.go @@ -42,14 +42,14 @@ var ( ErrBadEncryptionOptions = util.SystemError("invalid encryption options provided") ) -// 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.FscryptPolicyV1) error { +// 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. +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(unsafe.Pointer(policy))) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(arg)) switch errno { case 0: return nil @@ -61,7 +61,6 @@ func policyIoctl(file *os.File, request uintptr, policy *unix.FscryptPolicyV1) e // ENOENT was returned instead of ENODATA on some filesystems before v4.11. return ErrNotEncrypted case unix.EEXIST: - // EINVAL was returned instead of EEXIST on some filesystems before v4.11. return ErrEncrypted default: return errno @@ -75,6 +74,42 @@ var ( unix.FSCRYPT_POLICY_FLAGS_PAD_16, unix.FSCRYPT_POLICY_FLAGS_PAD_32} ) +// flagsToPadding returns the amount of padding specified in the policy flags. +func flagsToPadding(flags uint8) int64 { + paddingFlag := int64(flags & unix.FS_POLICY_FLAGS_PAD_MASK) + + // This lookup should always succeed + padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) + if !ok { + log.Panicf("padding flag of %x not found", paddingFlag) + } + return padding +} + +func buildV1PolicyData(policy *unix.FscryptPolicyV1) *PolicyData { + return &PolicyData{ + KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), + Options: &EncryptionOptions{ + Padding: flagsToPadding(policy.Flags), + Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), + Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), + PolicyVersion: 1, + }, + } +} + +func buildV2PolicyData(policy *unix.FscryptPolicyV2) *PolicyData { + return &PolicyData{ + KeyDescriptor: hex.EncodeToString(policy.Master_key_identifier[:]), + Options: &EncryptionOptions{ + Padding: flagsToPadding(policy.Flags), + Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), + Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), + PolicyVersion: 2, + }, + } +} + // GetPolicy returns the Policy data for the given directory or file (includes // the KeyDescriptor and the encryption options). Returns an error if the // path is not encrypted or the policy couldn't be retrieved. @@ -85,28 +120,36 @@ func GetPolicy(path string) (*PolicyData, error) { } defer file.Close() - var policy unix.FscryptPolicyV1 - if err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil { + // First try the new version of the ioctl. This works for both v1 and v2 policies. + var arg unix.FscryptGetPolicyExArg + 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 { + // 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) } - - // Convert the padding flag into an amount of padding - paddingFlag := int64(policy.Flags & unix.FSCRYPT_POLICY_FLAGS_PAD_MASK) - - // This lookup should always succeed - padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) - if !ok { - log.Panicf("padding flag of %x not found", paddingFlag) + switch arg.Policy[0] { // arg.policy.version + case unix.FSCRYPT_POLICY_V1: + if arg.Size != uint64(unsafe.Sizeof(unix.FscryptPolicyV1{})) { + // should never happen + return nil, errors.New("unexpected size for v1 policy") + } + return buildV1PolicyData((*unix.FscryptPolicyV1)(policyPtr)), nil + case unix.FSCRYPT_POLICY_V2: + if arg.Size != uint64(unsafe.Sizeof(unix.FscryptPolicyV2{})) { + // should never happen + return nil, errors.New("unexpected size for v2 policy") + } + return buildV2PolicyData((*unix.FscryptPolicyV2)(policyPtr)), nil + default: + return nil, errors.Errorf("unsupported encryption policy version [%d]", + arg.Policy[0]) } - - return &PolicyData{ - KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), - Options: &EncryptionOptions{ - Padding: padding, - Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), - Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), - }, - }, nil } // For improved performance, use the DIRECT_KEY flag when using ciphers that @@ -121,6 +164,52 @@ func shouldUseDirectKeyFlag(options *EncryptionOptions) bool { return options.Contents == EncryptionOptions_Adiantum } +func buildPolicyFlags(options *EncryptionOptions) uint8 { + // This lookup should always succeed (as policy is valid) + flags, ok := util.Lookup(options.Padding, paddingArray, flagsArray) + if !ok { + log.Panicf("padding of %d was not found", options.Padding) + } + if shouldUseDirectKeyFlag(options) { + flags |= unix.FSCRYPT_POLICY_FLAG_DIRECT_KEY + } + return uint8(flags) +} + +func setV1Policy(file *os.File, options *EncryptionOptions, descriptorBytes []byte) error { + policy := unix.FscryptPolicyV1{ + Version: unix.FSCRYPT_POLICY_V1, + Contents_encryption_mode: uint8(options.Contents), + Filenames_encryption_mode: uint8(options.Filenames), + Flags: uint8(buildPolicyFlags(options)), + } + + // The descriptor should always be the correct length (as policy is valid) + if len(descriptorBytes) != unix.FSCRYPT_KEY_DESCRIPTOR_SIZE { + log.Panic("wrong descriptor size for v1 policy") + } + copy(policy.Master_key_descriptor[:], descriptorBytes) + + return policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, unsafe.Pointer(&policy)) +} + +func setV2Policy(file *os.File, options *EncryptionOptions, descriptorBytes []byte) error { + policy := unix.FscryptPolicyV2{ + Version: unix.FSCRYPT_POLICY_V2, + Contents_encryption_mode: uint8(options.Contents), + Filenames_encryption_mode: uint8(options.Filenames), + Flags: uint8(buildPolicyFlags(options)), + } + + // The descriptor should always be the correct length (as policy is valid) + if len(descriptorBytes) != unix.FSCRYPT_KEY_IDENTIFIER_SIZE { + log.Panic("wrong descriptor size for v2 policy") + } + copy(policy.Master_key_identifier[:], descriptorBytes) + + return policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, unsafe.Pointer(&policy)) +} + // SetPolicy sets up the specified directory to be encrypted with the specified // policy. Returns an error if we cannot set the policy for any reason (not a // directory, invalid options or KeyDescriptor, etc). @@ -135,30 +224,21 @@ func SetPolicy(path string, data *PolicyData) error { return errors.Wrap(err, "invalid policy") } - // This lookup should always succeed (as policy is valid) - flags, 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 errors.New("invalid descriptor: " + data.KeyDescriptor) - } - - if shouldUseDirectKeyFlag(data.Options) { - flags |= unix.FSCRYPT_POLICY_FLAG_DIRECT_KEY + return errors.New("invalid key descriptor: " + data.KeyDescriptor) } - policy := unix.FscryptPolicyV1{ - Version: unix.FSCRYPT_POLICY_V1, - Contents_encryption_mode: uint8(data.Options.Contents), - Filenames_encryption_mode: uint8(data.Options.Filenames), - Flags: uint8(flags), + switch data.Options.PolicyVersion { + case 1: + err = setV1Policy(file, data.Options, descriptorBytes) + case 2: + err = setV2Policy(file, data.Options, descriptorBytes) + default: + err = errors.Errorf("policy version of %d is invalid", data.Options.PolicyVersion) } - copy(policy.Master_key_descriptor[:], descriptorBytes) - if err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); err == unix.EINVAL { + 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 // the correct error due to a potential race condition on path. @@ -195,7 +275,7 @@ func CheckSupport(path string) error { Flags: math.MaxUint8, } - err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &badPolicy) + err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, unsafe.Pointer(&badPolicy)) switch err { case nil: log.Panicf(`FS_IOC_SET_ENCRYPTION_POLICY succeeded when it should have failed. |