Skip to content

Commit

Permalink
Try to detect process being forked during PAM transaction
Browse files Browse the repository at this point in the history
Update #350
  • Loading branch information
ebiggers committed Apr 8, 2022
1 parent f28f2be commit d60e19b
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pam/pam.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,7 @@ void freeSecret(pam_handle_t* pamh, char* data, int error_status) {
void infoMessage(pam_handle_t* pamh, const char* message) {
pam_info(pamh, "%s", message);
}

void syslogMessage(pam_handle_t* pamh, int priority, const char* message) {
pam_syslog(pamh, priority, "%s", message);
}
18 changes: 18 additions & 0 deletions pam/pam.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import "C"
import (
"errors"
"log"
"log/syslog"
"os/user"
"unsafe"

Expand Down Expand Up @@ -127,6 +128,16 @@ func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) {
return data, nil
}

// GetServiceName retrieves the name of application running the PAM transaction.
func (h *Handle) GetServiceName() string {
var data unsafe.Pointer
h.status = C.pam_get_item(h.handle, C.int(Service), &data)
if err := h.err(); err != nil {
return "[unknown service]"
}
return C.GoString((*C.char)(data))
}

// StartAsPamUser sets the effective privileges to that of the PAM user.
func (h *Handle) StartAsPamUser() error {
userPrivs, err := security.UserPrivileges(h.PamUser)
Expand Down Expand Up @@ -173,6 +184,13 @@ func (h *Handle) InfoMessage(message string) {
C.infoMessage(h.handle, cMessage)
}

// SyslogMessage logs a message to the system log using pam_syslog().
func (h *Handle) SyslogMessage(priority syslog.Priority, message string) {
cMessage := C.CString(message)
defer C.free(unsafe.Pointer(cMessage))
C.syslogMessage(h.handle, C.int(priority), cMessage)
}

// Transaction represents a wrapped pam_handle_t type created with pam_start
// from an application.
type Transaction Handle
Expand Down
3 changes: 3 additions & 0 deletions pam/pam.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@ void freeSecret(pam_handle_t *pamh, char *data, int error_status);
// Sends a message to the application using pam_info().
void infoMessage(pam_handle_t *pamh, const char *message);

// Logs a message to the system log using pam_syslog().
void syslogMessage(pam_handle_t *pamh, int priority, const char *message);

#endif // FSCRYPT_PAM_H
44 changes: 44 additions & 0 deletions pam_fscrypt/pam_fscrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import "C"
import (
"fmt"
"log"
"log/syslog"
"os"
"strconv"
"unsafe"

"github.com/pkg/errors"
Expand All @@ -46,6 +49,8 @@ const (
moduleName = "pam_fscrypt"
// authtokLabel tags the AUTHTOK in the PAM data.
authtokLabel = "fscrypt_authtok"
// pidLabel tags the pid in the PAM data.
pidLabel = "fscrypt_pid"
// These flags are used to toggle behavior of the PAM module.
debugFlag = "debug"

Expand Down Expand Up @@ -76,6 +81,12 @@ func Authenticate(handle *pam.Handle, _ map[string]bool) error {
}
defer handle.StopAsPamUser()

// Save the PID in the PAM data so that the Session hook can try to
// detect the unsupported situation where the process was forked.
if err := handle.SetString(pidLabel, strconv.Itoa(os.Getpid())); err != nil {
return errors.Wrap(err, "could not save pid in PAM data")
}

// If this user doesn't have a login protector, no unlocking is needed.
if _, err := loginProtector(handle); err != nil {
log.Printf("no protector, no need for AUTHTOK: %s", err)
Expand Down Expand Up @@ -128,6 +139,35 @@ func setupUserKeyringIfNeeded(handle *pam.Handle, policies []*actions.Policy) er
return handle.StartAsPamUser()
}

// The Go runtime doesn't support being forked, as it is multithreaded but
// fork() deletes all threads except one. Some programs, such as xrdp, misuse
// libpam by fork()-ing the process between pam_authenticate() and
// pam_open_session(). Try to detect such unsupported cases and bail out early
// rather than deadlocking the Go runtime, which would prevent the user from
// logging in entirely. This isn't guaranteed to work, as we are already
// running Go code here, so we may have already deadlocked. But in practice the
// deadlock doesn't occur until hashing the login passphrase is attempted.
func isUnsupportedFork(handle *pam.Handle) bool {
pidString, err := handle.GetString(pidLabel)
if err != nil {
return false
}
expectedPid, err := strconv.Atoi(pidString)
if err != nil {
log.Printf("%s parse error: %v", pidLabel, err)
return false
}
if os.Getpid() == expectedPid {
return false
}
handle.InfoMessage("pam_fscrypt couldn't automatically unlock directories, see syslog")
msg := fmt.Sprintf(`not unlocking directories because %s forked the
process between authenticating the user and opening the session, which is
incompatible with pam_fscrypt.`, handle.GetServiceName())
handle.SyslogMessage(syslog.LOG_WARNING, msg)
return true
}

// OpenSession provisions any policies protected with the login protector.
func OpenSession(handle *pam.Handle, _ map[string]bool) error {
// We will always clear the AUTHTOK data
Expand All @@ -154,6 +194,10 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error {
return nil
}

if isUnsupportedFork(handle) {
return nil
}

if err = setupUserKeyringIfNeeded(handle, policies); err != nil {
return errors.Wrapf(err, "setting up user keyring")
}
Expand Down

0 comments on commit d60e19b

Please sign in to comment.