From 74e870b7bd1585b4b509da47e0e75db66336e576 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 23 Feb 2022 12:35:04 -0800 Subject: Strictly validate metadata file ownership by default The metadata validation checks introduced by the previous commits are good, but to reduce the attack surface it would be much better to avoid reading and parsing files owned by other users in the first place. There are some possible use cases for users sharing fscrypt metadata files, but I think that for the vast majority of users it is unneeded and just opens up attack surface. Thus, make fscrypt (and pam_fscrypt) not process policies or protectors owned by other users by default. Specifically, * If fscrypt or pam_fscrypt is running as a non-root user, only policies and protectors owned by the user or by root can be used. * If fscrypt is running as root, any policy or protector can be used. (This is to match user expectations -- starting a sudo session should gain rights, not remove rights.) * If pam_fscrypt is running as root, only policies and protectors owned by root can be used. Note that this only applies when the root user themselves has an fscrypt login protector, which is rare. Add an option 'allow_cross_user_metadata' to /etc/fscrypt.conf which allows restoring the old behavior for anyone who really needs it. --- pam_fscrypt/run_fscrypt.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'pam_fscrypt') diff --git a/pam_fscrypt/run_fscrypt.go b/pam_fscrypt/run_fscrypt.go index ef7ff92..8c640ce 100644 --- a/pam_fscrypt/run_fscrypt.go +++ b/pam_fscrypt/run_fscrypt.go @@ -137,6 +137,13 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { if err != nil { return nil, err } + // Ensure that pam_fscrypt only processes metadata files owned by the + // user or root, even if the user is root themselves. (Normally, when + // fscrypt is run as root it is allowed to process all metadata files. + // This implements stricter behavior for pam_fscrypt.) + if !ctx.Config.GetAllowCrossUserMetadata() { + ctx.TrustedUser = handle.PamUser + } // Find the user's PAM protector. options, err := ctx.ProtectorOptions() @@ -164,10 +171,11 @@ func policiesUsingProtector(protector *actions.Protector) []*actions.Policy { var policies []*actions.Policy for _, mount := range mounts { // Skip mountpoints that do not use the protector. - if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { + if _, _, err := mount.GetProtector(protector.Descriptor(), + protector.Context.TrustedUser); err != nil { continue } - policyDescriptors, err := mount.ListPolicies() + policyDescriptors, err := mount.ListPolicies(protector.Context.TrustedUser) if err != nil { log.Printf("listing policies: %s", err) continue -- cgit v1.2.3 From 9871a39409222a80b4c4c22cbaab17bae84f1712 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 23 Feb 2022 12:35:04 -0800 Subject: pam_fscrypt: log errors getting protector in policiesUsingProtector() If the error is anything other than ErrNotSetup, it might be helpful to know what is going on. --- pam_fscrypt/run_fscrypt.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'pam_fscrypt') diff --git a/pam_fscrypt/run_fscrypt.go b/pam_fscrypt/run_fscrypt.go index 8c640ce..a563ab5 100644 --- a/pam_fscrypt/run_fscrypt.go +++ b/pam_fscrypt/run_fscrypt.go @@ -173,6 +173,9 @@ func policiesUsingProtector(protector *actions.Protector) []*actions.Policy { // Skip mountpoints that do not use the protector. if _, _, err := mount.GetProtector(protector.Descriptor(), protector.Context.TrustedUser); err != nil { + if _, ok := err.(*filesystem.ErrNotSetup); !ok { + log.Print(err) + } continue } policyDescriptors, err := mount.ListPolicies(protector.Context.TrustedUser) -- cgit v1.2.3 From 97700817e737eabf45033cdb4a42fa5c6e74f877 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 23 Feb 2022 12:35:04 -0800 Subject: pam_fscrypt: ignore system users pam_fscrypt should never need to do anything for system users, so detect them early so that we can avoid wasting any resources looking for their login protector. --- pam_fscrypt/run_fscrypt.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'pam_fscrypt') diff --git a/pam_fscrypt/run_fscrypt.go b/pam_fscrypt/run_fscrypt.go index a563ab5..6b40854 100644 --- a/pam_fscrypt/run_fscrypt.go +++ b/pam_fscrypt/run_fscrypt.go @@ -35,6 +35,7 @@ import ( "log" "log/syslog" "os" + "os/user" "path/filepath" "runtime/debug" "unsafe" @@ -57,6 +58,10 @@ const ( countDirectoryPermissions = 0700 countFilePermissions = 0600 countFileFormat = "%d\n" + // uidMin is the first UID that can be used for a regular user (as + // opposed to a system user or root). This value is fairly standard + // across Linux distros, but it can be adjusted if needed. + uidMin = 1000 ) // PamFunc is used to define the various actions in the PAM module. @@ -67,6 +72,14 @@ type PamFunc struct { impl func(handle *pam.Handle, args map[string]bool) error } +// isSystemUser checks if a user is a system user. pam_fscrypt should never +// need to do anything for system users since they should never have login +// protectors. Therefore, we detect them early to avoid wasting resources. +func isSystemUser(user *user.User) bool { + uid := util.AtoiOrPanic(user.Uid) + return uid < uidMin && uid != 0 +} + // Run is used to convert between the Go functions and exported C funcs. func (f *PamFunc) Run(pamh unsafe.Pointer, argc C.int, argv **C.char) (ret C.int) { args := parseArgs(argc, argv) @@ -85,7 +98,13 @@ func (f *PamFunc) Run(pamh unsafe.Pointer, argc C.int, argv **C.char) (ret C.int log.Printf("%s(%v) starting", f.name, args) handle, err := pam.NewHandle(pamh) if err == nil { - err = f.impl(handle, args) + if isSystemUser(handle.PamUser) { + log.Printf("invoked for system user %q (%s), doing nothing", + handle.PamUser.Username, handle.PamUser.Uid) + err = nil + } else { + err = f.impl(handle, args) + } } if err != nil { fmt.Fprintf(errorWriter, "%s(%v) failed: %s", f.name, args, err) -- cgit v1.2.3