diff options
Diffstat (limited to 'actions/policy.go')
| -rw-r--r-- | actions/policy.go | 318 |
1 files changed, 256 insertions, 62 deletions
diff --git a/actions/policy.go b/actions/policy.go index cbdcb3a..d745f8b 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -1,5 +1,5 @@ /* - * protector.go - functions for dealing with policies + * policy.go - functions for dealing with policies * * Copyright 2017 Google Inc. * Author: Joe Richey (joerichey@google.com) @@ -22,46 +22,147 @@ package actions import ( "fmt" "log" + "os" + "os/user" "reflect" "github.com/pkg/errors" + "google.golang.org/protobuf/proto" "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" ) -// 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 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 removal to have an effect, the filesystem should -// also be unmounted. +// keyring. In order for this to fully take effect, the filesystem may also need +// to be unmounted or caches dropped. func PurgeAllPolicies(ctx *Context) error { if err := ctx.checkContext(); err != nil { return err } - policies, err := ctx.Mount.ListPolicies() + policies, err := ctx.Mount.ListPolicies(nil) if err != nil { return err } for _, policyDescriptor := range policies { - service := ctx.getService() - err = security.RemoveKey(service+policyDescriptor, ctx.TargetUser) - + err = keyring.RemoveEncryptionKey(policyDescriptor, ctx.getKeyringOptions(), false) switch errors.Cause(err) { - case nil, security.ErrKeySearch: + case nil, keyring.ErrKeyNotPresent: // We don't care if the key has already been removed + case keyring.ErrKeyFilesOpen: + log.Printf("Key for policy %s couldn't be fully removed because some files are still in-use", + policyDescriptor) + case keyring.ErrKeyAddedByOtherUsers: + log.Printf("Key for policy %s couldn't be fully removed because other user(s) have added it too", + policyDescriptor) default: return err } @@ -75,10 +176,12 @@ func PurgeAllPolicies(ctx *Context) error { // allow encrypted files to be accessed). As with the key struct, a Policy // should be wiped after use. type Policy struct { - Context *Context - data *metadata.PolicyData - key *crypto.Key - created bool + Context *Context + data *metadata.PolicyData + key *crypto.Key + created bool + ownerIfCreating *user.User + newLinkedProtectors []string } // CreatePolicy creates a Policy protected by given Protector and stores the @@ -94,16 +197,28 @@ func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) { return nil, err } + keyDescriptor, err := crypto.ComputeKeyDescriptor(key, ctx.Config.Options.PolicyVersion) + if err != nil { + key.Wipe() + return nil, err + } + policy := &Policy{ Context: ctx, data: &metadata.PolicyData{ Options: ctx.Config.Options, - KeyDescriptor: crypto.ComputeDescriptor(key), + KeyDescriptor: keyDescriptor, }, key: key, created: true, } + policy.ownerIfCreating, err = getOwnerOfMetadataForProtector(protector) + if err != nil { + policy.Lock() + return nil, err + } + if err = policy.AddProtector(protector); err != nil { policy.Lock() return nil, err @@ -119,7 +234,7 @@ func GetPolicy(ctx *Context, descriptor string) (*Policy, error) { if err := ctx.checkContext(); err != nil { return nil, err } - data, err := ctx.Mount.GetPolicy(descriptor) + data, err := ctx.Mount.GetPolicy(descriptor, ctx.TrustedUser) if err != nil { return nil, err } @@ -140,23 +255,35 @@ 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 + // is ambiguous with other errors, but try to detect this case + // and show a better error message. + if os.IsPermission(err) && + filesystem.HaveReadAccessTo(path) && + !keyring.IsFsKeyringSupported(ctx.Mount) { + return nil, &ErrAccessDeniedPossiblyV2{path} + } return nil, err } descriptor := pathData.KeyDescriptor log.Printf("found policy %s for %q", descriptor, path) - mountData, err := ctx.Mount.GetPolicy(descriptor) + mountData, err := ctx.Mount.GetPolicy(descriptor, ctx.TrustedUser) 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 !reflect.DeepEqual(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") @@ -188,15 +315,23 @@ func (policy *Policy) Descriptor() string { return policy.data.KeyDescriptor } -// Description returns the description that will be used when the key for this -// Policy is inserted into the keyring -func (policy *Policy) Description() string { - return policy.Context.getService() + policy.Descriptor() +// Options returns the encryption options of this policy. +func (policy *Policy) Options() *metadata.EncryptionOptions { + return policy.data.Options +} + +// Version returns the version of this policy. +func (policy *Policy) Version() int64 { + return policy.data.Options.PolicyVersion } -// Destroy removes a policy from the filesystem. The internal key should still -// be wiped with Lock(). +// Destroy removes a policy from the filesystem. It also removes any new +// protector links that were created for the policy. This does *not* wipe the +// policy's internal key from memory; use Lock() to do that. func (policy *Policy) Destroy() error { + for _, protectorDescriptor := range policy.newLinkedProtectors { + policy.Context.Mount.RemoveProtector(protectorDescriptor) + } return policy.Context.Mount.RemovePolicy(policy.Descriptor()) } @@ -260,7 +395,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 @@ -284,6 +419,25 @@ func (policy *Policy) UsesProtector(protector *Protector) bool { return ok } +// getOwnerOfMetadataForProtector returns the User to whom the owner of any new +// policies or protector links for the given protector should be set. +// +// This will return a non-nil value only when the protector is a login protector +// and the process is running as root. In this scenario, root is setting up +// encryption on the user's behalf, so we need to make new policies and +// protector links owned by the user (rather than root) to allow them to be read +// by the user, just like the login protector itself which is handled elsewhere. +func getOwnerOfMetadataForProtector(protector *Protector) (*user.User, error) { + if protector.data.Source == metadata.SourceType_pam_passphrase && util.IsUserRoot() { + owner, err := util.UserFromUID(protector.data.Uid) + if err != nil { + return nil, err + } + return owner, nil + } + return nil, nil +} + // AddProtector updates the data that is wrapping the Policy Key so that the // provided Protector is now protecting the specified Policy. If an error is // returned, no data has been changed. If the policy and protector are on @@ -291,7 +445,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 @@ -299,13 +453,22 @@ func (policy *Policy) AddProtector(protector *Protector) error { // If the protector is on a different filesystem, we need to add a link // to it on the policy's filesystem. - if policy.Context.Mount != protector.Context.Mount { + if !reflect.DeepEqual(policy.Context.Mount, protector.Context.Mount) { log.Printf("policy on %s\n protector on %s\n", policy.Context.Mount, protector.Context.Mount) - err := policy.Context.Mount.AddLinkedProtector( - protector.Descriptor(), protector.Context.Mount) + ownerIfCreating, err := getOwnerOfMetadataForProtector(protector) + if err != nil { + return err + } + isNewLink, err := policy.Context.Mount.AddLinkedProtector( + protector.Descriptor(), protector.Context.Mount, + protector.Context.TrustedUser, ownerIfCreating) if err != nil { return err } + if isNewLink { + policy.newLinkedProtectors = append(policy.newLinkedProtectors, + protector.Descriptor()) + } } else { log.Printf("policy and protector both on %q", policy.Context.Mount) } @@ -331,18 +494,19 @@ func (policy *Policy) AddProtector(protector *Protector) error { } // RemoveProtector updates the data that is wrapping the Policy Key so that the -// provided Protector is no longer protecting the specified Policy. If an error -// is returned, no data has been changed. Note that no protector links are +// protector with the given descriptor is no longer protecting the specified +// Policy. If an error is returned, no data has been changed. Note that the +// protector itself won't be removed, nor will a link to the protector be // removed (in the case where the protector and policy are on different -// filesystems). The policy and protector can be locked or unlocked. -func (policy *Policy) RemoveProtector(protector *Protector) error { - idx, ok := policy.findWrappedKeyIndex(protector.Descriptor()) +// filesystems). The policy can be locked or unlocked. +func (policy *Policy) RemoveProtector(protectorDescriptor string) error { + idx, ok := policy.findWrappedKeyIndex(protectorDescriptor) if !ok { - return ErrNotProtected + return &ErrNotProtected{policy.Descriptor(), protectorDescriptor} } if len(policy.data.WrappedPolicyKeys) == 1 { - return ErrOnlyProtector + return &ErrOnlyProtector{policy} } // Remove the wrapped key from the data @@ -362,18 +526,26 @@ func (policy *Policy) RemoveProtector(protector *Protector) error { 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 + } else if !reflect.DeepEqual(pathMount, policy.Context.Mount) { + return &ErrDifferentFilesystem{policy.Context.Mount, pathMount} } - return metadata.SetPolicy(path, policy.data) + err := metadata.SetPolicy(path, policy.data) + return policy.Context.Mount.EncryptionSupportError(err) } -// IsProvisioned returns a boolean indicating if the policy has its key in the -// keyring, meaning files and directories using this policy are accessible. -func (policy *Policy) IsProvisioned() bool { - _, err := security.FindKey(policy.Description(), policy.Context.TargetUser) - return err == nil +// GetProvisioningStatus returns the status of this policy's key in the keyring. +func (policy *Policy) GetProvisioningStatus() keyring.KeyStatus { + status, _ := keyring.GetEncryptionKeyStatus(policy.Descriptor(), + policy.Context.getKeyringOptions()) + return status +} + +// IsProvisionedByTargetUser returns true if the policy's key is present in the +// target kernel keyring, but not if that keyring is a filesystem keyring and +// the key only been added by users other than Context.TargetUser. +func (policy *Policy) IsProvisionedByTargetUser() bool { + return policy.GetProvisioningStatus() == keyring.KeyPresent } // Provision inserts the Policy key into the kernel keyring. This allows reading @@ -382,18 +554,40 @@ func (policy *Policy) Provision() error { if policy.key == nil { return ErrLocked } - return crypto.InsertPolicyKey(policy.key, policy.Description(), policy.Context.TargetUser) + return keyring.AddEncryptionKey(policy.key, policy.Descriptor(), + policy.Context.getKeyringOptions()) } // Deprovision removes the Policy key from the kernel keyring. This prevents -// reading and writing to the directory once the caches are cleared. -func (policy *Policy) Deprovision() error { - return security.RemoveKey(policy.Description(), policy.Context.TargetUser) +// reading and writing to the directory --- unless the target keyring is a user +// keyring, in which case caches must be dropped too. If the Policy key was +// already removed, returns keyring.ErrKeyNotPresent. +func (policy *Policy) Deprovision(allUsers bool) error { + return keyring.RemoveEncryptionKey(policy.Descriptor(), + policy.Context.getKeyringOptions(), allUsers) +} + +// NeedsUserKeyring returns true if Provision and Deprovision for this policy +// will use a user keyring (deprecated), not a filesystem keyring. +func (policy *Policy) NeedsUserKeyring() bool { + return policy.Version() == 1 && !policy.Context.Config.GetUseFsKeyringForV1Policies() +} + +// NeedsRootToProvision returns true if Provision and Deprovision will require +// root for this policy in the current configuration. +func (policy *Policy) NeedsRootToProvision() bool { + return policy.Version() == 1 && policy.Context.Config.GetUseFsKeyringForV1Policies() +} + +// CanBeAppliedWithoutProvisioning returns true if this process can apply this +// policy to a directory without first calling Provision. +func (policy *Policy) CanBeAppliedWithoutProvisioning() bool { + return policy.Version() == 1 || util.IsUserRoot() } // commitData writes the Policy's current data to the filesystem. func (policy *Policy) commitData() error { - return policy.Context.Mount.AddPolicy(policy.data) + return policy.Context.Mount.AddPolicy(policy.data, policy.ownerIfCreating) } // findWrappedPolicyKey returns the index of the wrapped policy key @@ -413,7 +607,7 @@ func (policy *Policy) addKey(toAdd *metadata.WrappedPolicyKey) { policy.data.WrappedPolicyKeys = append(policy.data.WrappedPolicyKeys, toAdd) } -// remove removes the wrapped policy key at the specified index. This +// removeKey removes the wrapped policy key at the specified index. This // does not preserve the order of the wrapped policy key array. If no index is // specified the last key is removed. func (policy *Policy) removeKey(index int) *metadata.WrappedPolicyKey { |