From f3f1d2f98de26e8180c56d87aaad0e4e98fb4e47 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 19 Jul 2017 15:40:35 -0700 Subject: Various small nits a helper functions for PAM --- .gitignore | 4 +++- actions/policy.go | 8 +++++++- crypto/key.go | 1 - pam/login.go | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2491d40..34880d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -/fscrypt +fscrypt +fscrypt.* fscrypt_image +pam_fscrypt.so diff --git a/actions/policy.go b/actions/policy.go index bf1f593..461f8cc 100644 --- a/actions/policy.go +++ b/actions/policy.go @@ -278,13 +278,19 @@ func (policy *Policy) Lock() error { return err } +// UsesProtector returns if the policy is protected with the protector +func (policy *Policy) UsesProtector(protector *Protector) bool { + _, ok := policy.findWrappedKeyIndex(protector.Descriptor()) + return ok +} + // 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 // different filesystems, a link will be created between them. The policy and // protector must both be unlocked. func (policy *Policy) AddProtector(protector *Protector) error { - if _, ok := policy.findWrappedKeyIndex(protector.Descriptor()); ok { + if policy.UsesProtector(protector) { return ErrAlreadyProtected } if policy.key == nil || protector.key == nil { diff --git a/crypto/key.go b/crypto/key.go index 656e6dc..497a0ef 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -175,7 +175,6 @@ func (key *Key) resize(requestedSize int) (*Key, error) { // string allocated by C. Note that this method is unsafe as this C copy has no // locking or wiping functionality. The key shouldn't contain any `\0` bytes. func (key *Key) UnsafeToCString() unsafe.Pointer { - // Memory for the key must be moved into a C string allocated by C. size := C.size_t(key.Len()) data := C.calloc(size+1, 1) C.memcpy(data, util.Ptr(key.data), size) diff --git a/pam/login.go b/pam/login.go index e89ee01..346edd4 100644 --- a/pam/login.go +++ b/pam/login.go @@ -38,7 +38,7 @@ import ( // Pam error values var ( - ErrPAMPassphrase = errors.New("incorrect login passphrase") + ErrPassphrase = errors.New("incorrect login passphrase") ) // Global state is needed for the PAM callback, so we guard this function with a @@ -107,7 +107,7 @@ func IsUserLoginToken(username string, token *crypto.Key, quiet bool) error { } if !authenticated { - return ErrPAMPassphrase + return ErrPassphrase } return nil } -- cgit v1.2.3 From d117249a29af31a51ae56f64943635cbc0104cea Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 19 Jul 2017 15:41:23 -0700 Subject: pam_fscrypt: The actual PAM module and config --- pam_fscrypt/config | 13 ++ pam_fscrypt/pam_fscrypt.go | 367 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 pam_fscrypt/config create mode 100644 pam_fscrypt/pam_fscrypt.go diff --git a/pam_fscrypt/config b/pam_fscrypt/config new file mode 100644 index 0000000..26b7767 --- /dev/null +++ b/pam_fscrypt/config @@ -0,0 +1,13 @@ +Name: fscrypt PAM passphrase support +Default: yes +Priority: 0 +Auth-Type: Additional +Auth-Final: + optional pam_fscrypt.so +Session-Type: Additional +Session-Interactive-Only: yes +Session-Final: + optional pam_fscrypt.so drop_caches +Password-Type: Additional +Password-Final: + optional pam_fscrypt.so diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go new file mode 100644 index 0000000..09e9664 --- /dev/null +++ b/pam_fscrypt/pam_fscrypt.go @@ -0,0 +1,367 @@ +/* + * pam_fscrypt.go - Checks the validity of a login token key against PAM. + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package main + +/* +#cgo LDFLAGS: -lpam -fPIC + +#include +#include + +#include +*/ +import "C" +import ( + "fmt" + "io" + "io/ioutil" + "log" + "log/syslog" + "os" + "unsafe" + + "github.com/pkg/errors" + + "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/pam" + "github.com/google/fscrypt/util" +) + +const ( + // These labels are used to tag items in the PAM data. + authtokLabel = "fscrypt_authtok" + descriptorLabel = "fscrypt_descriptor" + provisionedKeysLabel = "fscrypt_provisioned_keys" + moduleName = "pam_fscrypt" +) + +// parseArgs takes a list of C arguments into a PAM function and returns a map +// where a key has a value of true if it appears in the argument list. +func parseArgs(argc C.int, argv **C.char) map[string]bool { + args := make(map[string]bool) + for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] { + args[C.GoString((*C.char)(cString))] = true + } + return args +} + +// setupLogging directs turns off standard logging (or redirects it to debug +// syslog if the "debug" argument is passed) and returns a writer to the error +// syslog. +func setupLogging(args map[string]bool) io.Writer { + log.SetFlags(0) // Syslog already includes time data itself + log.SetOutput(ioutil.Discard) + if args["debug"] { + debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) + if err == nil { + log.SetOutput(debugWriter) + } + } + + errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName) + if err != nil { + return ioutil.Discard + } + return errorWriter +} + +// loginProtector returns the login protector corresponding to the PAM_USER if +// one exists. This protector descriptor (if found) will be cached in the pam +// data, under descriptorLabel. +func loginProtector(handle *pam.Handle) (*actions.Protector, error) { + ctx, err := actions.NewContextFromMountpoint("/") + if err != nil { + return nil, err + } + + // Retrieve the cached value if one exists. + if descriptor, err := handle.GetString(descriptorLabel); err == nil { + log.Printf("using cached descriptor %q", descriptor) + return actions.GetProtector(ctx, descriptor) + } + + // Find the user's PAM protector. + pamUID, err := handle.GetUID() + if err != nil { + return nil, err + } + options, err := ctx.ProtectorOptions() + if err != nil { + return nil, err + } + for _, option := range options { + if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != pamUID { + continue + } + + log.Printf("caching descriptor %q", option.Descriptor()) + if err = handle.SetString(descriptorLabel, option.Descriptor()); err != nil { + log.Printf("could not set descriptor data: %s", err) + // We can still get the protector, so no error. + } + + return actions.GetProtectorFromOption(ctx, option) + } + return nil, fmt.Errorf("no PAM protector on %q", ctx.Mount.Path) +} + +// pam_sm_authenticate copies the AUTHTOK (if necessary) into the PAM data so it +// can be used in pam_sm_open_session. +//export pam_sm_authenticate +func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + errWriter := setupLogging(parseArgs(argc, argv)) + + // If this user doesn't have a login protector, no unlocking is needed. + if _, err := loginProtector(handle); err != nil { + log.Printf("no need to copy AUTHTOK: %s", err) + return C.PAM_SUCCESS + } + + log.Print("copying AUTHTOK in pam_sm_authenticate()") + authtok, err := handle.GetItem(pam.Authtok) + if err != nil { + fmt.Fprintf(errWriter, "could not get AUTHTOK: %s", err) + return C.PAM_SERVICE_ERR + } + if err = handle.SetSecret(authtokLabel, authtok); err != nil { + fmt.Fprintf(errWriter, "could not set AUTHTOK data: %s", err) + return C.PAM_SERVICE_ERR + } + return C.PAM_SUCCESS +} + +// pam_sm_stecred needed because we use pam_sm_authenticate. +//export pam_sm_setcred +func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return C.PAM_SUCCESS +} + +// policiesToProvision searches all the mountpoints for any unprovisioned +// policies protected with the specified protector. An error during this search +// does not halt the search, instead the errors are written to errWriter. +func policiesToProvision(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { + mounts, err := filesystem.AllFilesystems() + if err != nil { + fmt.Fprint(errWriter, err) + return nil + } + + var policies []*actions.Policy + for _, mount := range mounts { + // Skip mountpoints that do not use the protector. + if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { + continue + } + policyDescriptors, err := mount.ListPolicies() + if err != nil { + fmt.Fprintf(errWriter, "listing policies: %s", err) + continue + } + + ctx := &actions.Context{Config: protector.Context.Config, Mount: mount} + for _, policyDescriptor := range policyDescriptors { + policy, err := actions.GetPolicy(ctx, policyDescriptor) + if err != nil { + fmt.Fprintf(errWriter, "reading policy: %s", err) + continue + } + + if policy.UsesProtector(protector) && !policy.IsProvisioned() { + policies = append(policies, policy) + } + } + } + return policies +} + +// pam_sm_open_session provisions policies protected with the login protector. +//export pam_sm_open_session +func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + errWriter := setupLogging(parseArgs(argc, argv)) + + protector, err := loginProtector(handle) + if err != nil { + log.Printf("no directories to unlock: %s", err) + return C.PAM_SUCCESS + } + + keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { + if retry { + // Login passphrase and login protector have diverged. + // We could prompt the user for the old passphrase and + // rewrap, but we currently don't. + return nil, pam.ErrPassphrase + } + + authtok, err := handle.GetSecret(authtokLabel) + if err != nil { + // pam_sm_authenticate was not run before the session is + // opened. This can happen when a user does something + // like "sudo su ". We could prompt for the + // login passphrase here, but we currently don't. + return nil, errors.Wrap(err, "AUTHTOK data missing") + } + defer handle.ClearData(authtokLabel) + return crypto.NewKeyFromCString(authtok) + } + + log.Print("searching for policies to provision in pam_sm_open_session()") + policies := policiesToProvision(protector, errWriter) + + if len(policies) == 0 { + log.Print("no policies to provision") + return C.PAM_SUCCESS + } + + if err := protector.Unlock(keyFn); err != nil { + fmt.Fprintf(errWriter, "unlocking protector %s: %s", protector.Descriptor(), err) + return C.PAM_SERVICE_ERR + } + defer protector.Lock() + + var provisionedKeys []string + for _, policy := range policies { + if err := policy.UnlockWithProtector(protector); err != nil { + fmt.Fprintf(errWriter, "unlocking policy %s: %s", policy.Descriptor(), err) + continue + } + defer policy.Lock() + + if err := policy.Provision(); err != nil { + fmt.Fprintf(errWriter, "provisioning policy %s: %s", policy.Descriptor(), err) + continue + } + + log.Printf("policy %s provisioned", policy.Descriptor()) + provisionedKeys = append(provisionedKeys, policy.Description()) + } + + if len(provisionedKeys) == 0 { + fmt.Fprint(errWriter, "could not provision any policies") + return C.PAM_SERVICE_ERR + } + + if err := handle.SetSlice(provisionedKeysLabel, provisionedKeys); err != nil { + fmt.Fprintf(errWriter, "setting key list data: %s", err) + return C.PAM_SERVICE_ERR + } + return C.PAM_SUCCESS +} + +// pam_sm_close_session deprovisions all keys provisioned at the start of the +// session. It also clears the cache so these changes take effect. +//export pam_sm_close_session +func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + args := parseArgs(argc, argv) + errWriter := setupLogging(args) + + provisionedKeys, err := handle.GetSlice(provisionedKeysLabel) + if err != nil { + log.Printf("no directories to lock: %s", err) + return C.PAM_SUCCESS + } + + log.Print("locking directories in pam_sm_close_session()") + for _, provisionedKey := range provisionedKeys { + if err := crypto.RemovePolicyKey(provisionedKey); err != nil { + fmt.Fprintf(errWriter, "can't remove %s: %s", provisionedKey, err) + } + } + + if args["drop_caches"] { + log.Print("dropping page caches") + // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt + f, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) + if err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + defer f.Close() + // "3" clears slab objects and the page cache + if _, err := f.WriteString("3"); err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + } + + return C.PAM_SUCCESS +} + +// pam_sm_chauthtok rewraps the login protector when the passphrase changes. +//export pam_sm_chauthtok +func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + errWriter := setupLogging(parseArgs(argc, argv)) + + // Only do rewrapping if we have both AUTHTOKs and a login protector. + if pam.Flag(flags)&pam.PrelimCheck != 0 { + log.Print("no preliminary checks need to run") + return C.PAM_SUCCESS + } + protector, err := loginProtector(handle) + if err != nil { + log.Printf("no protector to rewrap: %s", err) + return C.PAM_SUCCESS + } + + oldKeyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { + if retry { + // If the OLDAUTHTOK disagrees with the login protector, + // we do nothing, as the protector will (probably) still + // disagree after the login passphrase changes. + return nil, pam.ErrPassphrase + } + authtok, err := handle.GetItem(pam.Oldauthtok) + if err != nil { + return nil, errors.Wrap(err, "could not get OLDAUTHTOK") + } + return crypto.NewKeyFromCString(authtok) + } + + newKeyFn := func(_ actions.ProtectorInfo, _ bool) (*crypto.Key, error) { + authtok, err := handle.GetItem(pam.Authtok) + if err != nil { + return nil, errors.Wrap(err, "could not get AUTHTOK") + } + return crypto.NewKeyFromCString(authtok) + } + + log.Print("rewrapping protector in pam_sm_chauthtok()") + if err = protector.Unlock(oldKeyFn); err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + defer protector.Lock() + if err = protector.Rewrap(newKeyFn); err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + + return C.PAM_SUCCESS +} + +// main() is needed to make a shared library compile +func main() {} -- cgit v1.2.3 From 5c853fd7317d337ab7dc3b9bfe533a22eb713e1f Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 19 Jul 2017 15:42:31 -0700 Subject: Updated documentation and build system for PAM --- CONTRIBUTING.md | 7 +++--- Makefile | 34 ++++++++++++++++++++++------- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1470fa4..7272b10 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,9 +30,10 @@ these commands when writing your code. ### Building and Testing -As mentioned in `README.md`, running `make` will build the fscrypt executable. -Running `make go` will build each package and run the tests, but just running -`make go` with nothing else will skip the integration tests. +As mentioned in `README.md`, running `make` will build the fscrypt executable +and the PAM module `pam_fscrypt.so`. Running `make go` will build each package +and run the tests, but just running `make go` with nothing else will skip the +integration tests. To run the integration tests, you will need a filesystem that supports encryption. If you already have some empty filesystem at `/foo/bar`, just run: diff --git a/Makefile b/Makefile index 4ebbe32..c5b43ce 100644 --- a/Makefile +++ b/Makefile @@ -16,11 +16,16 @@ # the License. NAME = fscrypt +PAM_NAME = pam_$(NAME) +PAM_MODULE = $(PAM_NAME).so -INSTALL = install -DESTDIR = /usr/local/bin +INSTALL ?= install +DESTDIR ?= /usr/local/bin +PAM_MODULE_DIR ?= /lib/security +PAM_CONFIG_DIR ?= /usr/share/pam-configs CMD_PKG = github.com/google/$(NAME)/cmd/$(NAME) +PAM_PKG = github.com/google/$(NAME)/$(PAM_NAME) SRC_FILES = $(shell find . -type f -name '*.go' -o -name "*.h" -o -name "*.c") GO_FILES = $(shell find . -type f -name '*.go' -not -path "./vendor/*") @@ -81,15 +86,20 @@ override GO_LINK_FLAGS += $(VERSION_FLAG) $(DATE_FLAG) -extldflags "$(LDFLAGS)" override GO_FLAGS += --ldflags '$(GO_LINK_FLAGS)' .PHONY: default all -default: $(NAME) + +default: $(NAME) $(PAM_MODULE) all: update format lint default test $(NAME): $(SRC_FILES) go build $(GO_FLAGS) -o $(NAME) $(CMD_PKG) +$(PAM_MODULE): $(SRC_FILES) + go build -buildmode=c-shared $(GO_FLAGS) -o $(PAM_MODULE) $(PAM_PKG) + rm -f $(PAM_NAME).h + .PHONY: clean clean: - rm -rf $(NAME) $(IMAGE) + rm -f $(NAME) $(PAM_MODULE) $(IMAGE) # Make sure go files build and tests pass. .PHONY: test @@ -129,14 +139,22 @@ lint: @golint $(GO_PKGS) | grep -v "pb.go" | ./input_fail.py @megacheck -unused.exported $(GO_PKGS) -.PHONY: install -install: $(NAME) +###### Installation commands ##### +.PHONY: install_bin install_pam install uninstall +install_bin: $(NAME) $(INSTALL) -d $(DESTDIR) $(INSTALL) $(NAME) $(DESTDIR) -.PHONY: uninstall +install_pam: $(PAM_MODULE) + $(INSTALL) -d $(PAM_MODULE_DIR) + $(INSTALL) $(PAM_MODULE) $(PAM_MODULE_DIR) + $(INSTALL) -d $(PAM_CONFIG_DIR) + $(INSTALL) $(PAM_NAME)/config $(PAM_CONFIG_DIR)/$(NAME) + +install: install_bin install_pam + uninstall: - rm -rf $(DESTDIR)/$(NAME) + rm -f $(DESTDIR)/$(NAME) $(PAM_MODULE_DIR)/$(PAM_MODULE) $(PAM_CONFIG_DIR)/$(NAME) # Install the go tools used for checking/generating the code .PHONY: go-tools diff --git a/README.md b/README.md index e2df8ca..4355239 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,6 @@ The following functionality is planned: * `fscrypt backup` - Manages backups of the fscrypt metadata * `fscrypt recovery` - Manages recovery keys for directories * `fscrypt cleanup` - Scans filesystem for unused policies/protectors -* A PAM module to support login passphrase changes (see below) See the example usage section below or run `fscrypt COMMAND --help` for more information about each of the commands. @@ -109,7 +108,7 @@ information about each of the commands. fscrypt has the following build dependencies: * [Go](https://golang.org/doc/install) * A C compiler (`gcc` or `clang`) -* `make` +* `make` * The [Argon2 Passphrase Hash](https://github.com/P-H-C/phc-winner-argon2) library, which can be [directly installed on Artful Ubuntu](https://packages.ubuntu.com/artful/libargon2-0-dev), @@ -155,6 +154,50 @@ fscrypt has the following runtime dependencies: The dynamic libraries are not needed if you built a static executable. +### Setting up the PAM module + +Note that to make use of the installed PAM module, your +[PAM configuration files](http://www.linux-pam.org/Linux-PAM-html/sag-configuration.html) +in `/etc/pam.d` must be modified to add fscrypt. + +#### Automatic setup on Ubuntu + +fscrypt automatically installs the +[PAM config file](https://wiki.ubuntu.com/PAMConfigFrameworkSpec) +`pam_fscrypt/config` to `/usr/share/pam-configs/fscrypt`. This file contains +reasonable defaults for the PAM module. To automatically apply these changes, +run `sudo pam-auth-update` and follow the on-screen instructions. + +#### Manual setup + +The fscrypt PAM module implements the Auth, Session, and Password +[types](http://www.linux-pam.org/Linux-PAM-html/sag-configuration-file.html). + +The Password functionality of `pam_fscrypt.so` is used to automatically rewrap +a user's login protector when their unix passphrase changes. An easy way to get +the working is to add the line: +``` +password optional pam_fscrypt.so +``` +after `pam_unix.so` in `/etc/pam.d/common-password` or similar. + +The Auth and Session functionality of `pam_fscrypt.so` are used to automatically +unlock directories when logging in as a user. An easy way to get this working is +to add the line: +``` +auth optional pam_fscrypt.so +``` +after `pam_unix.so` in `/etc/pam.d/common-password` or similar, and to add the +line: +``` +session optional pam_fscrypt.so drop_caches +``` +after `pam_unix.so` in `/etc/pam.d/common-session` or similar. The `drop_caches` +option tells fscrypt to clear the filesystem caches on session closes if some +directories were unlocked. This ensures all unlocked data is inaccessible after +session close. All the types also support the `debug` option which prints +additional debug information to the syslog. + ## Note about stability fscrypt follows [semantic versioning](http://semver.org). As such, all versions @@ -507,14 +550,25 @@ best to help. #### I changed my login passphrase, now all my directories are inaccessible -We do not currently support the changing of the login passphrase. This will -change when the appropriate module is completed. Until then, you can fix it by -first finding the necessary protector (with `fscrypt status PATH`) and then -running: +The PAM module provided by fscrypt (`pam_fscrypt.so`) should automatically +detect changes to a user's login passphrase so that they can still access their +encrypted directories. However, sometimes the login passphrase can become +desynchronized from a user's login protector. This usually happens when the PAM +passphrase is managed by an external system, if the PAM module is not installed, +or if the PAM module is not properly configured. + +To fix your login protector, you first should find the appropriate protector ID +by running `fscrypt status "/"`. Then, change the passphrase for this protector +by running: ``` -fscrypt metadata change-passphrase --protector=MOUNTPOINT:ID +fscrypt metadata change-passphrase --protector=/:ID ``` +#### Directories using my login passphrase are not automatically unlocking. + +Either the PAM module is not installed correctly, or your login passphrase +changed and things got out of sync. + #### I can still see files or filenames after running `fscrypt purge MOUNTPOINT` You need to unmount `MOUNTPOINT` to clear the necessary caches. See -- cgit v1.2.3 From 32c9be59a2485ef44ac4b3accc2f102cf2eb5a39 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 22 Aug 2017 12:52:41 -0700 Subject: security: Moved cache dropping function --- cmd/fscrypt/commands.go | 4 ++-- security/cache.go | 41 +++++++++++++++++++++++++++++++++++++++++ security/privileges.go | 1 + util/util.go | 17 ----------------- 4 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 security/cache.go diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index e6c7f9a..3e8bc98 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -33,7 +33,7 @@ import ( "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" - "github.com/google/fscrypt/util" + "github.com/google/fscrypt/security" ) // Setup is a command which can to global or per-filesystem initialization. @@ -371,7 +371,7 @@ func purgeAction(c *cli.Context) error { fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path) if dropCachesFlag.Value { - if err = util.DropInodeCache(); err != nil { + if err = security.DropInodeCache(); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Global inode cache cleared.\n") diff --git a/security/cache.go b/security/cache.go new file mode 100644 index 0000000..7002014 --- /dev/null +++ b/security/cache.go @@ -0,0 +1,41 @@ +/* + * cache.go - Handles cache clearing and management. + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package security + +import ( + "log" + "os" +) + +// DropInodeCache instructs the kernel to clear the global cache of inodes and +// dentries. This has the effect of making encrypted directories whose keys +// are not present no longer accessible. Requires root privileges. +func DropInodeCache() error { + log.Print("dropping page caches") + // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt + file, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) + if err != nil { + return err + } + defer file.Close() + // "2" just clears the inodes and dentries + _, err = file.WriteString("2") + return err +} diff --git a/security/privileges.go b/security/privileges.go index f6e8098..aff41a7 100644 --- a/security/privileges.go +++ b/security/privileges.go @@ -18,6 +18,7 @@ */ // Package security manages: +// - Cache clearing (cache.go) // - Keyring Operations (keyring.go) // - Privilege manipulation (privileges.go) // - Maintaining the link between the root and user keyrings. diff --git a/util/util.go b/util/util.go index acdc3fc..14d23e2 100644 --- a/util/util.go +++ b/util/util.go @@ -25,7 +25,6 @@ package util import ( "bufio" - "log" "math" "os" "unsafe" @@ -98,19 +97,3 @@ func ReadLine() (string, error) { scanner.Scan() return scanner.Text(), scanner.Err() } - -// DropInodeCache instructs the kernel to clear the global cache of inodes and -// dentries. This has the effect of making encrypted directories whose keys -// are not present no longer accessible. Requires root privileges. -func DropInodeCache() error { - log.Print("dropping page caches") - // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt - file, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) - if err != nil { - return err - } - defer file.Close() - // "2" just clears the inodes and dentries - _, err = file.WriteString("2") - return err -} -- cgit v1.2.3 From ca70b81fddb340ed5212741a773c3a4a0c4ea3e2 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 22 Aug 2017 14:15:58 -0700 Subject: pam_fscrypt: Updated module to use new APIs --- pam_fscrypt/pam_fscrypt.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 09e9664..ad7cfdc 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -34,9 +34,10 @@ import ( "io/ioutil" "log" "log/syslog" - "os" "unsafe" + "golang.org/x/sys/unix" + "github.com/pkg/errors" "github.com/google/fscrypt/actions" @@ -44,6 +45,7 @@ import ( "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/pam" + "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -101,7 +103,7 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { } // Find the user's PAM protector. - pamUID, err := handle.GetUID() + uid := int64(unix.Geteuid()) if err != nil { return nil, err } @@ -110,7 +112,7 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { return nil, err } for _, option := range options { - if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != pamUID { + if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != uid { continue } @@ -286,22 +288,13 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) log.Print("locking directories in pam_sm_close_session()") for _, provisionedKey := range provisionedKeys { - if err := crypto.RemovePolicyKey(provisionedKey); err != nil { + if err := security.RemoveKey(provisionedKey); err != nil { fmt.Fprintf(errWriter, "can't remove %s: %s", provisionedKey, err) } } if args["drop_caches"] { - log.Print("dropping page caches") - // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt - f, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) - if err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR - } - defer f.Close() - // "3" clears slab objects and the page cache - if _, err := f.WriteString("3"); err != nil { + if err = security.DropInodeCache(); err != nil { fmt.Fprint(errWriter, err) return C.PAM_SERVICE_ERR } -- cgit v1.2.3 From ef5cc07774674c66b5dbeb7c655a26ac6371e378 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 22 Aug 2017 15:41:18 -0700 Subject: pam_fscrypt: lock all PAM policies w/ flag --- pam/pam.go | 35 ------------------- pam_fscrypt/config | 2 +- pam_fscrypt/pam_fscrypt.go | 84 ++++++++++++++++++++-------------------------- 3 files changed, 38 insertions(+), 83 deletions(-) diff --git a/pam/pam.go b/pam/pam.go index 9188b6e..804171d 100644 --- a/pam/pam.go +++ b/pam/pam.go @@ -32,8 +32,6 @@ import ( "errors" "fmt" "unsafe" - - "github.com/google/fscrypt/util" ) // Handle wraps the C pam_handle_t type. This is used from within modules. @@ -99,39 +97,6 @@ func (h *Handle) GetString(name string) (string, error) { return C.GoString((*C.char)(data)), nil } -// SetSlice sets a []string value for the PAM data with the specified name. -func (h *Handle) SetSlice(name string, slice []string) error { - sliceLength := uintptr(len(slice)) - memorySize := (sliceLength + 1) * unsafe.Sizeof(uintptr(0)) - data := C.malloc(C.size_t(memorySize)) - - cSlice := util.PointerSlice(data) - for i, str := range slice { - cSlice[i] = unsafe.Pointer(C.CString(str)) - } - cSlice[sliceLength] = nil - - return h.setData(name, data, C.CleanupFunc(C.freeArray)) -} - -// GetSlice gets a []string value for the PAM data with the specified name. It -// should have been previously set with SetSlice(). -func (h *Handle) GetSlice(name string) ([]string, error) { - data, err := h.getData(name) - if err != nil { - return nil, err - } - - var slice []string - for _, cString := range util.PointerSlice(data) { - if cString == nil { - return slice, nil - } - slice = append(slice, C.GoString((*C.char)(cString))) - } - panic("We will never get here") -} - // GetItem retrieves a PAM information item. This a pointer directory to the // data, so it shouldn't be modified. func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) { diff --git a/pam_fscrypt/config b/pam_fscrypt/config index 26b7767..795a4f8 100644 --- a/pam_fscrypt/config +++ b/pam_fscrypt/config @@ -7,7 +7,7 @@ Auth-Final: Session-Type: Additional Session-Interactive-Only: yes Session-Final: - optional pam_fscrypt.so drop_caches + optional pam_fscrypt.so drop_caches lock_policies Password-Type: Additional Password-Final: optional pam_fscrypt.so diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index ad7cfdc..84b848e 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -50,11 +50,14 @@ import ( ) const ( + moduleName = "pam_fscrypt" // These labels are used to tag items in the PAM data. - authtokLabel = "fscrypt_authtok" - descriptorLabel = "fscrypt_descriptor" - provisionedKeysLabel = "fscrypt_provisioned_keys" - moduleName = "pam_fscrypt" + authtokLabel = "fscrypt_authtok" + descriptorLabel = "fscrypt_descriptor" + // These flags are used to toggle behavior of the PAM module. + debugFlag = "debug" + lockFlag = "lock_policies" + cacheFlag = "drop_caches" ) // parseArgs takes a list of C arguments into a PAM function and returns a map @@ -73,7 +76,7 @@ func parseArgs(argc C.int, argv **C.char) map[string]bool { func setupLogging(args map[string]bool) io.Writer { log.SetFlags(0) // Syslog already includes time data itself log.SetOutput(ioutil.Discard) - if args["debug"] { + if args[debugFlag] { debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) if err == nil { log.SetOutput(debugWriter) @@ -96,12 +99,6 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { return nil, err } - // Retrieve the cached value if one exists. - if descriptor, err := handle.GetString(descriptorLabel); err == nil { - log.Printf("using cached descriptor %q", descriptor) - return actions.GetProtector(ctx, descriptor) - } - // Find the user's PAM protector. uid := int64(unix.Geteuid()) if err != nil { @@ -112,17 +109,9 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { return nil, err } for _, option := range options { - if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != uid { - continue + if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { + return actions.GetProtectorFromOption(ctx, option) } - - log.Printf("caching descriptor %q", option.Descriptor()) - if err = handle.SetString(descriptorLabel, option.Descriptor()); err != nil { - log.Printf("could not set descriptor data: %s", err) - // We can still get the protector, so no error. - } - - return actions.GetProtectorFromOption(ctx, option) } return nil, fmt.Errorf("no PAM protector on %q", ctx.Mount.Path) } @@ -159,10 +148,10 @@ func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int return C.PAM_SUCCESS } -// policiesToProvision searches all the mountpoints for any unprovisioned -// policies protected with the specified protector. An error during this search -// does not halt the search, instead the errors are written to errWriter. -func policiesToProvision(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { +// policiesUsingProtector searches all the mountpoints for any policies +// protected with the specified protector. An error during this search does not +// halt the search, instead the errors are written to errWriter. +func policiesUsingProtector(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { mounts, err := filesystem.AllFilesystems() if err != nil { fmt.Fprint(errWriter, err) @@ -189,7 +178,7 @@ func policiesToProvision(protector *actions.Protector, errWriter io.Writer) []*a continue } - if policy.UsesProtector(protector) && !policy.IsProvisioned() { + if policy.UsesProtector(protector) { policies = append(policies, policy) } } @@ -205,7 +194,7 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) protector, err := loginProtector(handle) if err != nil { - log.Printf("no directories to unlock: %s", err) + log.Printf("no pam protector for this user: %s", err) return C.PAM_SUCCESS } @@ -229,11 +218,10 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) return crypto.NewKeyFromCString(authtok) } - log.Print("searching for policies to provision in pam_sm_open_session()") - policies := policiesToProvision(protector, errWriter) - + log.Print("searching for policies to unlock in pam_sm_open_session()") + policies := policiesUsingProtector(protector, errWriter) if len(policies) == 0 { - log.Print("no policies to provision") + log.Print("no policies to unlock") return C.PAM_SUCCESS } @@ -243,8 +231,11 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) } defer protector.Lock() - var provisionedKeys []string for _, policy := range policies { + if policy.IsProvisioned() { + log.Printf("policy %s already provisioned", policy.Descriptor()) + continue + } if err := policy.UnlockWithProtector(protector); err != nil { fmt.Fprintf(errWriter, "unlocking policy %s: %s", policy.Descriptor(), err) continue @@ -257,18 +248,8 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) } log.Printf("policy %s provisioned", policy.Descriptor()) - provisionedKeys = append(provisionedKeys, policy.Description()) } - if len(provisionedKeys) == 0 { - fmt.Fprint(errWriter, "could not provision any policies") - return C.PAM_SERVICE_ERR - } - - if err := handle.SetSlice(provisionedKeysLabel, provisionedKeys); err != nil { - fmt.Fprintf(errWriter, "setting key list data: %s", err) - return C.PAM_SERVICE_ERR - } return C.PAM_SUCCESS } @@ -280,10 +261,19 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) args := parseArgs(argc, argv) errWriter := setupLogging(args) - provisionedKeys, err := handle.GetSlice(provisionedKeysLabel) - if err != nil { - log.Printf("no directories to lock: %s", err) - return C.PAM_SUCCESS + if args[lockFlag] { + protector, err := loginProtector(handle) + if err != nil { + log.Printf("no pam protector for this user: %s", err) + return C.PAM_SUCCESS + } + + policies := policiesUsingProtector(protector, errWriter) + + if len(policies) == 0 { + log.Print("no policies to lock") + return C.PAM_SUCCESS + } } log.Print("locking directories in pam_sm_close_session()") @@ -293,7 +283,7 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) } } - if args["drop_caches"] { + if args[cacheFlag] { if err = security.DropInodeCache(); err != nil { fmt.Fprint(errWriter, err) return C.PAM_SERVICE_ERR -- cgit v1.2.3 From b15792b8d7c197d84970415fd2525c51aee3996c Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 23 Aug 2017 12:29:10 -0700 Subject: Added some documentation and improved security API --- CONTRIBUTING.md | 10 ++++++++++ README.md | 4 ++-- pam/pam.go | 24 ++++++++++++++++++------ security/keyring.go | 12 ++++++++++-- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7272b10..6b7be43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,16 @@ You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. +## Reporting an Issue + +Any bugs or problems found in fscrypt should be reported though the +[Github Issue Tracker](https://github.com/google/fscrypt/issues/new). When +reporting an issue, be sure to give as much information about the problem as +possible. If reporting an issue around the fscrypt command-line tool, post the +relevant output from fscrypt, running with the `--verbose` flag. For the +pam_fscrypt module, use the `debug` flag with the module and post the relevant +parts of the syslog (at ``). + ## Code reviews All submissions, including submissions by project members, require review. We diff --git a/README.md b/README.md index 4355239..cb1a1e3 100644 --- a/README.md +++ b/README.md @@ -545,8 +545,8 @@ file for more information about singing the CLA and submitting a pull request. ## Troubleshooting In general, if you are encountering issues with fscrypt, -[open an issue](https://github.com/google/fscrypt/issues/new). We will try our -best to help. +[open an issue](https://github.com/google/fscrypt/issues/new), following the +guidelines in `CONTRIBUTING.md`. We will try our best to help. #### I changed my login passphrase, now all my directories are inaccessible diff --git a/pam/pam.go b/pam/pam.go index 804171d..3049efb 100644 --- a/pam/pam.go +++ b/pam/pam.go @@ -32,12 +32,15 @@ import ( "errors" "fmt" "unsafe" + + "github.com/google/fscrypt/security" ) // Handle wraps the C pam_handle_t type. This is used from within modules. type Handle struct { handle *C.pam_handle_t status C.int + privs *security.Privileges } // NewHandle creates a Handle from a raw pointer. @@ -105,19 +108,28 @@ func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) { return data, h.err() } -// GetIDs retrieves the UID and GID of the corresponding PAM_USER. -func (h *Handle) GetIDs() (uid int, gid int, err error) { +// DropThreadPrivileges sets the effective privileges to that of the PAM user +func (h *Handle) DropThreadPrivileges() error { var pamUsername *C.char + var err error + h.status = C.pam_get_user(h.handle, &pamUsername, nil) if err = h.err(); err != nil { - return 0, 0, err + return err } - pwnam := C.getpwnam(pamUsername) if pwnam == nil { - return 0, 0, fmt.Errorf("unknown user %q", C.GoString(pamUsername)) + return fmt.Errorf("unknown user %q", C.GoString(pamUsername)) } - return int(pwnam.pw_uid), int(pwnam.pw_gid), nil + + h.privs, err = security.DropThreadPrivileges(int(pwnam.pw_uid), int(pwnam.pw_gid)) + return err +} + +// RaiseThreadPrivileges restores the original privileges that were running the +// PAM module (this is usually root). +func (h *Handle) RaiseThreadPrivileges() error { + return security.RaiseThreadPrivileges(h.privs) } func (h *Handle) err() error { diff --git a/security/keyring.go b/security/keyring.go index f75b189..28225b0 100644 --- a/security/keyring.go +++ b/security/keyring.go @@ -151,11 +151,19 @@ func getUserKeyringID() (int, error) { func keyringLink(keyID int, keyringID int) error { _, err := unix.KeyctlInt(unix.KEYCTL_LINK, keyID, keyringID, 0, 0) log.Printf("KeyctlLink(%d, %d) = %v", keyID, keyringID, err) - return errors.Wrap(ErrKeyringLink, err.Error()) + + if err != nil { + return errors.Wrap(ErrKeyringLink, err.Error()) + } + return err } func keyringUnlink(keyID int, keyringID int) error { _, err := unix.KeyctlInt(unix.KEYCTL_UNLINK, keyID, keyringID, 0, 0) log.Printf("KeyctlUnlink(%d, %d) = %v", keyID, keyringID, err) - return errors.Wrap(ErrKeyringUnlink, err.Error()) + + if err != nil { + return errors.Wrap(ErrKeyringUnlink, err.Error()) + } + return err } -- cgit v1.2.3 From d617d7725ce8b91df2152d6539da10c401c59325 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 23 Aug 2017 18:51:23 -0700 Subject: pam_fscrypt: Session accounting completed --- pam/pam.go | 45 ++++--- pam_fscrypt/pam_fscrypt.go | 322 ++++++++++++++++++--------------------------- pam_fscrypt/run_fscrypt.go | 210 +++++++++++++++++++++++++++++ util/util.go | 8 ++ 4 files changed, 376 insertions(+), 209 deletions(-) create mode 100644 pam_fscrypt/run_fscrypt.go diff --git a/pam/pam.go b/pam/pam.go index 3049efb..12f2e97 100644 --- a/pam/pam.go +++ b/pam/pam.go @@ -31,6 +31,7 @@ import "C" import ( "errors" "fmt" + "log" "unsafe" "github.com/google/fscrypt/security" @@ -41,14 +42,32 @@ type Handle struct { handle *C.pam_handle_t status C.int privs *security.Privileges + // UID of the user being authenticated + UID int + // GID of the user being authenticated + GID int } // NewHandle creates a Handle from a raw pointer. -func NewHandle(pamh unsafe.Pointer) *Handle { - return &Handle{ +func NewHandle(pamh unsafe.Pointer) (*Handle, error) { + h := &Handle{ handle: (*C.pam_handle_t)(pamh), status: C.PAM_SUCCESS, } + + var pamUsername *C.char + h.status = C.pam_get_user(h.handle, &pamUsername, nil) + if err := h.err(); err != nil { + return nil, err + } + + pwnam := C.getpwnam(pamUsername) + if pwnam == nil { + return nil, fmt.Errorf("unknown user %q", C.GoString(pamUsername)) + } + h.UID = int(pwnam.pw_uid) + h.GID = int(pwnam.pw_gid) + return h, nil } func (h *Handle) setData(name string, data unsafe.Pointer, cleanup C.CleanupFunc) error { @@ -110,26 +129,20 @@ func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) { // DropThreadPrivileges sets the effective privileges to that of the PAM user func (h *Handle) DropThreadPrivileges() error { - var pamUsername *C.char var err error - - h.status = C.pam_get_user(h.handle, &pamUsername, nil) - if err = h.err(); err != nil { - return err - } - pwnam := C.getpwnam(pamUsername) - if pwnam == nil { - return fmt.Errorf("unknown user %q", C.GoString(pamUsername)) - } - - h.privs, err = security.DropThreadPrivileges(int(pwnam.pw_uid), int(pwnam.pw_gid)) + h.privs, err = security.DropThreadPrivileges(h.UID, h.GID) return err } // RaiseThreadPrivileges restores the original privileges that were running the -// PAM module (this is usually root). +// PAM module (this is usually root). As this error is often ignored in a defer +// statement, any error is also logged. func (h *Handle) RaiseThreadPrivileges() error { - return security.RaiseThreadPrivileges(h.privs) + err := security.RaiseThreadPrivileges(h.privs) + if err != nil { + log.Print(err) + } + return err } func (h *Handle) err() error { diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 84b848e..2eecd3a 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -29,175 +29,77 @@ package main */ import "C" import ( - "fmt" - "io" - "io/ioutil" "log" - "log/syslog" "unsafe" - "golang.org/x/sys/unix" - "github.com/pkg/errors" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" - "github.com/google/fscrypt/filesystem" - "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/pam" "github.com/google/fscrypt/security" - "github.com/google/fscrypt/util" ) const ( moduleName = "pam_fscrypt" - // These labels are used to tag items in the PAM data. - authtokLabel = "fscrypt_authtok" - descriptorLabel = "fscrypt_descriptor" + // authtokLabel tags the AUTHTOK in the PAM data. + authtokLabel = "fscrypt_authtok" // These flags are used to toggle behavior of the PAM module. debugFlag = "debug" lockFlag = "lock_policies" cacheFlag = "drop_caches" ) -// parseArgs takes a list of C arguments into a PAM function and returns a map -// where a key has a value of true if it appears in the argument list. -func parseArgs(argc C.int, argv **C.char) map[string]bool { - args := make(map[string]bool) - for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] { - args[C.GoString((*C.char)(cString))] = true - } - return args -} - -// setupLogging directs turns off standard logging (or redirects it to debug -// syslog if the "debug" argument is passed) and returns a writer to the error -// syslog. -func setupLogging(args map[string]bool) io.Writer { - log.SetFlags(0) // Syslog already includes time data itself - log.SetOutput(ioutil.Discard) - if args[debugFlag] { - debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) - if err == nil { - log.SetOutput(debugWriter) - } - } - - errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName) - if err != nil { - return ioutil.Discard - } - return errorWriter -} - -// loginProtector returns the login protector corresponding to the PAM_USER if -// one exists. This protector descriptor (if found) will be cached in the pam -// data, under descriptorLabel. -func loginProtector(handle *pam.Handle) (*actions.Protector, error) { - ctx, err := actions.NewContextFromMountpoint("/") - if err != nil { - return nil, err - } - - // Find the user's PAM protector. - uid := int64(unix.Geteuid()) - if err != nil { - return nil, err - } - options, err := ctx.ProtectorOptions() - if err != nil { - return nil, err - } - for _, option := range options { - if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { - return actions.GetProtectorFromOption(ctx, option) - } +// Authenticate copies the AUTHTOK (if necessary) into the PAM data so it can be +// used in pam_sm_open_session. +func Authenticate(handle *pam.Handle, _ map[string]bool) error { + if err := handle.DropThreadPrivileges(); err != nil { + return err } - return nil, fmt.Errorf("no PAM protector on %q", ctx.Mount.Path) -} - -// pam_sm_authenticate copies the AUTHTOK (if necessary) into the PAM data so it -// can be used in pam_sm_open_session. -//export pam_sm_authenticate -func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - errWriter := setupLogging(parseArgs(argc, argv)) + defer handle.RaiseThreadPrivileges() // If this user doesn't have a login protector, no unlocking is needed. if _, err := loginProtector(handle); err != nil { log.Printf("no need to copy AUTHTOK: %s", err) - return C.PAM_SUCCESS + return nil } - log.Print("copying AUTHTOK in pam_sm_authenticate()") + log.Print("Authenticate: copying AUTHTOK for use in the session") authtok, err := handle.GetItem(pam.Authtok) if err != nil { - fmt.Fprintf(errWriter, "could not get AUTHTOK: %s", err) - return C.PAM_SERVICE_ERR - } - if err = handle.SetSecret(authtokLabel, authtok); err != nil { - fmt.Fprintf(errWriter, "could not set AUTHTOK data: %s", err) - return C.PAM_SERVICE_ERR + return errors.Wrap(err, "could not get AUTHTOK") } - return C.PAM_SUCCESS + err = handle.SetSecret(authtokLabel, authtok) + return errors.Wrap(err, "could not set AUTHTOK data") } -// pam_sm_stecred needed because we use pam_sm_authenticate. -//export pam_sm_setcred -func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - return C.PAM_SUCCESS -} - -// policiesUsingProtector searches all the mountpoints for any policies -// protected with the specified protector. An error during this search does not -// halt the search, instead the errors are written to errWriter. -func policiesUsingProtector(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { - mounts, err := filesystem.AllFilesystems() - if err != nil { - fmt.Fprint(errWriter, err) - return nil +// OpenSession provisions any policies protected with the login protector. +func OpenSession(handle *pam.Handle, _ map[string]bool) error { + // We will always clear the the AUTHTOK data + defer handle.ClearData(authtokLabel) + // Increment the count as we add a session + if _, err := AdjustCount(handle, 1); err != nil { + return err } - var policies []*actions.Policy - for _, mount := range mounts { - // Skip mountpoints that do not use the protector. - if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { - continue - } - policyDescriptors, err := mount.ListPolicies() - if err != nil { - fmt.Fprintf(errWriter, "listing policies: %s", err) - continue - } - - ctx := &actions.Context{Config: protector.Context.Config, Mount: mount} - for _, policyDescriptor := range policyDescriptors { - policy, err := actions.GetPolicy(ctx, policyDescriptor) - if err != nil { - fmt.Fprintf(errWriter, "reading policy: %s", err) - continue - } - - if policy.UsesProtector(protector) { - policies = append(policies, policy) - } - } + if err := handle.DropThreadPrivileges(); err != nil { + return err } - return policies -} - -// pam_sm_open_session provisions policies protected with the login protector. -//export pam_sm_open_session -func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - errWriter := setupLogging(parseArgs(argc, argv)) + defer handle.RaiseThreadPrivileges() + // If there are no polices for the login protector, no unlocking needed. protector, err := loginProtector(handle) if err != nil { - log.Printf("no pam protector for this user: %s", err) - return C.PAM_SUCCESS + log.Printf("nothing to unlock: %s", err) + return nil + } + policies := policiesUsingProtector(protector) + if len(policies) == 0 { + log.Print("no policies to unlock") + return nil } + log.Print("OpenSession: unlocking policies protected with AUTHTOK") keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { if retry { // Login passphrase and login protector have diverged. @@ -214,100 +116,107 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) // login passphrase here, but we currently don't. return nil, errors.Wrap(err, "AUTHTOK data missing") } - defer handle.ClearData(authtokLabel) - return crypto.NewKeyFromCString(authtok) - } - log.Print("searching for policies to unlock in pam_sm_open_session()") - policies := policiesUsingProtector(protector, errWriter) - if len(policies) == 0 { - log.Print("no policies to unlock") - return C.PAM_SUCCESS + return crypto.NewKeyFromCString(authtok) } - if err := protector.Unlock(keyFn); err != nil { - fmt.Fprintf(errWriter, "unlocking protector %s: %s", protector.Descriptor(), err) - return C.PAM_SERVICE_ERR + return errors.Wrapf(err, "unlocking protector %s", protector.Descriptor()) } defer protector.Lock() + // We don't stop provisioning polices on error, we try all of them. for _, policy := range policies { if policy.IsProvisioned() { log.Printf("policy %s already provisioned", policy.Descriptor()) continue } if err := policy.UnlockWithProtector(protector); err != nil { - fmt.Fprintf(errWriter, "unlocking policy %s: %s", policy.Descriptor(), err) + log.Printf("unlocking policy %s: %s", policy.Descriptor(), err) continue } defer policy.Lock() if err := policy.Provision(); err != nil { - fmt.Fprintf(errWriter, "provisioning policy %s: %s", policy.Descriptor(), err) + log.Printf("provisioning policy %s: %s", policy.Descriptor(), err) continue } - log.Printf("policy %s provisioned", policy.Descriptor()) } - - return C.PAM_SUCCESS + return nil } -// pam_sm_close_session deprovisions all keys provisioned at the start of the -// session. It also clears the cache so these changes take effect. -//export pam_sm_close_session -func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - args := parseArgs(argc, argv) - errWriter := setupLogging(args) +// CloseSession can deprovision all keys provisioned at the start of the +// session. It can also clear the cache so these changes take effect. +func CloseSession(handle *pam.Handle, args map[string]bool) error { + // Only do stuff on session close when we are the last session + if count, err := AdjustCount(handle, -1); err != nil || count != 0 { + return err + } + var errLock, errCache error + // Don't automatically drop privileges, we may need them to drop caches. if args[lockFlag] { - protector, err := loginProtector(handle) - if err != nil { - log.Printf("no pam protector for this user: %s", err) - return C.PAM_SUCCESS - } + log.Print("CloseSession: locking polices protected with login") + errLock = lockLoginPolicies(handle) + } - policies := policiesUsingProtector(protector, errWriter) + if args[cacheFlag] { + log.Print("CloseSession: dropping inode caches") + errCache = security.DropInodeCache() + } - if len(policies) == 0 { - log.Print("no policies to lock") - return C.PAM_SUCCESS - } + if errLock != nil { + return errLock } + return errCache +} - log.Print("locking directories in pam_sm_close_session()") - for _, provisionedKey := range provisionedKeys { - if err := security.RemoveKey(provisionedKey); err != nil { - fmt.Fprintf(errWriter, "can't remove %s: %s", provisionedKey, err) - } +// lockLoginPolicies deprovisions all policy keys that are protected by +// the user's login protector. +func lockLoginPolicies(handle *pam.Handle) error { + if err := handle.DropThreadPrivileges(); err != nil { + return err } + defer handle.RaiseThreadPrivileges() - if args[cacheFlag] { - if err = security.DropInodeCache(); err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR - } + // If there are no polices for the login protector, no locking needed. + protector, err := loginProtector(handle) + if err != nil { + log.Printf("nothing to lock: %s", err) + return nil + } + policies := policiesUsingProtector(protector) + if len(policies) == 0 { + log.Print("no policies to lock") + return nil } - return C.PAM_SUCCESS + // We will try to deprovision all of the policies. + for _, policy := range policies { + if !policy.IsProvisioned() { + log.Printf("policy %s not provisioned", policy.Descriptor()) + continue + } + if err := policy.Deprovision(); err != nil { + log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), err) + continue + } + log.Printf("policy %s deprovisioned", policy.Descriptor()) + } + return nil } -// pam_sm_chauthtok rewraps the login protector when the passphrase changes. -//export pam_sm_chauthtok -func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - errWriter := setupLogging(parseArgs(argc, argv)) - - // Only do rewrapping if we have both AUTHTOKs and a login protector. - if pam.Flag(flags)&pam.PrelimCheck != 0 { - log.Print("no preliminary checks need to run") - return C.PAM_SUCCESS +// Chauthtok rewraps the login protector when the passphrase changes. +func Chauthtok(handle *pam.Handle, _ map[string]bool) error { + if err := handle.DropThreadPrivileges(); err != nil { + return err } + defer handle.RaiseThreadPrivileges() + protector, err := loginProtector(handle) if err != nil { - log.Printf("no protector to rewrap: %s", err) - return C.PAM_SUCCESS + log.Printf("nothing to rewrap: %s", err) + return nil } oldKeyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { @@ -332,19 +241,46 @@ func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.i return crypto.NewKeyFromCString(authtok) } - log.Print("rewrapping protector in pam_sm_chauthtok()") + log.Print("Chauthtok: rewrapping login protector") if err = protector.Unlock(oldKeyFn); err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR + return err } defer protector.Lock() - if err = protector.Rewrap(newKeyFn); err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR - } + return protector.Rewrap(newKeyFn) +} + +//export pam_sm_authenticate +func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return RunPamFunc(Authenticate, pamh, argc, argv) +} + +// pam_sm_stecred needed because we use pam_sm_authenticate. +//export pam_sm_setcred +func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { return C.PAM_SUCCESS } +//export pam_sm_open_session +func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return RunPamFunc(OpenSession, pamh, argc, argv) +} + +//export pam_sm_close_session +func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return RunPamFunc(CloseSession, pamh, argc, argv) +} + +//export pam_sm_chauthtok +func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + // Only do rewrapping if we have both AUTHTOKs and a login protector. + if pam.Flag(flags)&pam.PrelimCheck != 0 { + log.Print("no preliminary checks need to run") + return C.PAM_SUCCESS + } + + return RunPamFunc(Chauthtok, pamh, argc, argv) +} + // main() is needed to make a shared library compile func main() {} diff --git a/pam_fscrypt/run_fscrypt.go b/pam_fscrypt/run_fscrypt.go new file mode 100644 index 0000000..1527d42 --- /dev/null +++ b/pam_fscrypt/run_fscrypt.go @@ -0,0 +1,210 @@ +/* + * run_fscrypt.go - Helpers for running functions in the PAM module. + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package main + +/* +#cgo LDFLAGS: -lpam -fPIC + +#include +#include + +#include +*/ +import "C" +import ( + "fmt" + "io" + "io/ioutil" + "log" + "log/syslog" + "os" + "path/filepath" + "unsafe" + + "golang.org/x/sys/unix" + + "github.com/pkg/errors" + + "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/pam" + "github.com/google/fscrypt/util" +) + +const ( + // countDirectory is in a tmpfs filesystem so it will reset on reboot. + countDirectory = "/run/fscrypt" + // count files should only be readable and writable by root + countDirectoryPermissions = 0700 + countFilePermissions = 0600 + countFileFormat = "%d\n" +) + +// PamFunc is used to define the various actions in the PAM module +type PamFunc func(handle *pam.Handle, args map[string]bool) error + +// RunPamFunc is used to convert between the Go functions and exported C funcs. +func RunPamFunc(f PamFunc, pamh unsafe.Pointer, argc C.int, argv **C.char) C.int { + args := parseArgs(argc, argv) + errorWriter := setupLogging(args) + handle, err := pam.NewHandle(pamh) + + if err == nil { + err = f(handle, args) + } + + if err != nil { + fmt.Fprint(errorWriter, err) + return C.PAM_SERVICE_ERR + } + return C.PAM_SUCCESS +} + +// parseArgs takes a list of C arguments into a PAM function and returns a map +// where a key has a value of true if it appears in the argument list. +func parseArgs(argc C.int, argv **C.char) map[string]bool { + args := make(map[string]bool) + for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] { + args[C.GoString((*C.char)(cString))] = true + } + return args +} + +// setupLogging directs turns off standard logging (or redirects it to debug +// syslog if the "debug" argument is passed) and returns a writer to the error +// syslog. +func setupLogging(args map[string]bool) io.Writer { + log.SetFlags(0) // Syslog already includes time data itself + log.SetOutput(ioutil.Discard) + if args[debugFlag] { + debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) + if err == nil { + log.SetOutput(debugWriter) + } + } + + errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName) + if err != nil { + return ioutil.Discard + } + return errorWriter +} + +// loginProtector returns the login protector corresponding to the PAM_USER if +// one exists. This protector descriptor (if found) will be cached in the pam +// data, under descriptorLabel. +func loginProtector(handle *pam.Handle) (*actions.Protector, error) { + ctx, err := actions.NewContextFromMountpoint("/") + if err != nil { + return nil, err + } + + // Find the user's PAM protector. + options, err := ctx.ProtectorOptions() + if err != nil { + return nil, err + } + for _, option := range options { + if option.Source() == metadata.SourceType_pam_passphrase && + option.UID() == int64(handle.UID) { + return actions.GetProtectorFromOption(ctx, option) + } + } + return nil, errors.Errorf("no PAM protector for UID=%d on %q", handle.UID, ctx.Mount.Path) +} + +// policiesUsingProtector searches all the mountpoints for any policies +// protected with the specified protector. +func policiesUsingProtector(protector *actions.Protector) []*actions.Policy { + mounts, err := filesystem.AllFilesystems() + if err != nil { + log.Print(err) + return nil + } + + var policies []*actions.Policy + for _, mount := range mounts { + // Skip mountpoints that do not use the protector. + if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { + continue + } + policyDescriptors, err := mount.ListPolicies() + if err != nil { + log.Printf("listing policies: %s", err) + continue + } + + ctx := &actions.Context{Config: protector.Context.Config, Mount: mount} + for _, policyDescriptor := range policyDescriptors { + policy, err := actions.GetPolicy(ctx, policyDescriptor) + if err != nil { + log.Printf("reading policy: %s", err) + continue + } + + if policy.UsesProtector(protector) { + policies = append(policies, policy) + } + } + } + return policies +} + +// AdjustCount changes the session count for the pam user by the specified +// amount. If the count file does not exist, create it as if it had a count of +// zero. If the adjustment would be the count below zero, the count is set to +// zero. The value of the new count is returned. Requires root privileges. +func AdjustCount(handle *pam.Handle, delta int) (int, error) { + // Make sure the directory exists + if err := os.MkdirAll(countDirectory, countDirectoryPermissions); err != nil { + return 0, err + } + + path := filepath.Join(countDirectory, fmt.Sprintf("%d.count", handle.UID)) + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, countFilePermissions) + if err != nil { + return 0, err + } + if err := unix.Flock(int(file.Fd()), unix.LOCK_EX); err != nil { + return 0, err + } + defer file.Close() + + newCount := util.MaxInt(getCount(file)+delta, 0) + if _, err = file.Seek(0, io.SeekStart); err != nil { + return 0, err + } + if _, err = fmt.Fprintf(file, countFileFormat, newCount); err != nil { + return 0, err + } + + log.Printf("Session count for UID=%d updated to %d", handle.UID, newCount) + return newCount, nil +} + +// Returns the count in the file (or zero if the count cannot be read). +func getCount(file *os.File) int { + var count int + if _, err := fmt.Fscanf(file, countFileFormat, &count); err != nil { + return 0 + } + return count +} diff --git a/util/util.go b/util/util.go index 14d23e2..c02ea0e 100644 --- a/util/util.go +++ b/util/util.go @@ -82,6 +82,14 @@ func MinInt(a, b int) int { return b } +// MaxInt returns the greater of a and b. +func MaxInt(a, b int) int { + if a > b { + return a + } + return b +} + // MinInt64 returns the lesser of a and b. func MinInt64(a, b int64) int64 { if a < b { -- cgit v1.2.3 From 7fbff9a4d531e33f3d7c7e0b9871c2e19a55bace Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Wed, 23 Aug 2017 23:46:54 -0700 Subject: security: fscrypt now possesses the user keyring --- README.md | 15 ++++++++------- security/keyring.go | 7 +++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cb1a1e3..342fe66 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Once all the dependencies are installed, you can get the repository by running: go get -d github.com/google/fscrypt/... ``` and then you can run `make` in `$GOPATH/github.com/google/fscrypt` to build the -executable in that directory. Running `sudo make install` installs the binary to +executable and PAM moudle in that directory. Running `sudo make install` installs the binary to `/usr/local/bin`. See the `Makefile` for instructions on how to customize the build. This includes @@ -190,13 +190,14 @@ auth optional pam_fscrypt.so after `pam_unix.so` in `/etc/pam.d/common-password` or similar, and to add the line: ``` -session optional pam_fscrypt.so drop_caches +session optional pam_fscrypt.so drop_caches lock_policies ``` -after `pam_unix.so` in `/etc/pam.d/common-session` or similar. The `drop_caches` -option tells fscrypt to clear the filesystem caches on session closes if some -directories were unlocked. This ensures all unlocked data is inaccessible after -session close. All the types also support the `debug` option which prints -additional debug information to the syslog. +after `pam_unix.so` in `/etc/pam.d/common-session` or similar. The +`lock_policies` option locks the directories protected with the user's login +passphrase when the last session ends. The `drop_caches` option tells fscrypt to +clear the filesystem caches when the last session closes, ensuring all the +locked data is inaccessible. All the types also support the `debug` option which +prints additional debug information to the syslog. ## Note about stability diff --git a/security/keyring.go b/security/keyring.go index 28225b0..ef56364 100644 --- a/security/keyring.go +++ b/security/keyring.go @@ -141,6 +141,13 @@ func getUserKeyringID() (int, error) { } keyringID := int(parsedID) + // For some stupid reason, a thread does not automaticaly "possess" keys + // in the user keyring. So we link it into the process keyring so that + // we will not get "permission denied" when purging or modifying keys. + if err := keyringLink(keyringID, unix.KEY_SPEC_PROCESS_KEYRING); err != nil { + return 0, err + } + keyringIDCache[euid] = keyringID return keyringID, nil } -- cgit v1.2.3 From 19c13e861996c3503be5b0dc5a2cecfe186b1744 Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Thu, 24 Aug 2017 00:29:54 -0700 Subject: Updated documentation for PAM module help --- CONTRIBUTING.md | 4 ++-- README.md | 9 ++++----- pam_fscrypt/pam_fscrypt.go | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b7be43..357661c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,8 +22,8 @@ Any bugs or problems found in fscrypt should be reported though the reporting an issue, be sure to give as much information about the problem as possible. If reporting an issue around the fscrypt command-line tool, post the relevant output from fscrypt, running with the `--verbose` flag. For the -pam_fscrypt module, use the `debug` flag with the module and post the relevant -parts of the syslog (at ``). +pam_fscrypt module, use the `debug` option with the module and post the relevant +parts of the syslog (usually at `/var/log/syslog`). ## Code reviews diff --git a/README.md b/README.md index 342fe66..2214dad 100644 --- a/README.md +++ b/README.md @@ -568,12 +568,11 @@ fscrypt metadata change-passphrase --protector=/:ID #### Directories using my login passphrase are not automatically unlocking. Either the PAM module is not installed correctly, or your login passphrase -changed and things got out of sync. +changed and things got out of sync. Another reason that these directories might +not unlock is if your session starts without password authentication. The most +common case of this is public-key ssh login. -#### I can still see files or filenames after running `fscrypt purge MOUNTPOINT` - -You need to unmount `MOUNTPOINT` to clear the necessary caches. See -`fscrypt purge --help` for more information +To trigger a password authentication event, run `su $(whoami) -c exit`. #### Getting "encryption not enabled" on an ext4 filesystem. diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 2eecd3a..21bc779 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -78,7 +78,7 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { // We will always clear the the AUTHTOK data defer handle.ClearData(authtokLabel) // Increment the count as we add a session - if _, err := AdjustCount(handle, 1); err != nil { + if _, err := AdjustCount(handle, +1); err != nil { return err } @@ -150,6 +150,7 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { func CloseSession(handle *pam.Handle, args map[string]bool) error { // Only do stuff on session close when we are the last session if count, err := AdjustCount(handle, -1); err != nil || count != 0 { + log.Printf("count is %d and we are not locking", count) return err } @@ -275,7 +276,6 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { // Only do rewrapping if we have both AUTHTOKs and a login protector. if pam.Flag(flags)&pam.PrelimCheck != 0 { - log.Print("no preliminary checks need to run") return C.PAM_SUCCESS } -- cgit v1.2.3