← Back to Blogs
HN Story

Modernizing Authentication: Writing Linux-PAM Modules in GNU Guile

May 10, 2026

Modernizing Authentication: Writing Linux-PAM Modules in GNU Guile

For over three decades, Linux-PAM (Pluggable Authentication Modules) has served as the bedrock of authentication on Linux systems. Its design was revolutionary for its time, allowing security-relevant applications like su or login to follow consistent policies without requiring recompilation whenever a policy changed. However, the reliance on shared C objects often makes the process of writing and maintaining custom modules cumbersome and opaque.

Guile-PAM emerges as a modern alternative, enabling system administrators to write, configure, and maintain PAM modules using GNU Guile, a dialect of Scheme. By bridging the gap between the low-level PAM API and a high-level interpreted language, Guile-PAM transforms authentication logic from static compiled binaries into flexible, readable scripts.

The Architecture of Guile-PAM

At its core, Guile-PAM operates via a specially crafted shared object, pam_guile.so. This object acts as a gateway, calling GNU Guile scripts to handle authentication tasks. This architecture allows for three distinct levels of integration:

  1. Task-Specific Modules: Using pam_guile.so as a standard Linux-PAM shared object to perform a single specific task in Guile.
  2. Service Replacement: Replacing an entire PAM recipe (a specific service) by making pam_guile.so the sole shared object and handling all logic within Guile.
  3. System-Wide Orchestration: Replacing all services in /etc/pam.d with pam_guile.so, effectively moving both recipe selection and logic into the Guile environment.

Understanding "Pamdas"

In Guile-PAM, the fundamental unit of logic is the Pamda (a portmanteau of PAM and Lambda). A Pamda is an anonymous procedure with the following signature:

(lambda (action handle flags options) ...)
  • Action: One of six symbols (e.g., pam_sm_authenticate, pam_sm_open_session) representing the PAM entry point.
  • Handle: An opaque variable providing access to internal PAM data, such as the username and the conversation function.
  • Flags: An integer bitmask.
  • Options: A list of strings passed from the service definition.

Reimplementing the PAM Stack

One of the most powerful features of Guile-PAM is its complete reimplementation of the Linux-PAM stack in Scheme. Instead of relying on the static configuration files of Linux-PAM, developers can define "gates" and "plans" programmatically.

Gates and Plans

A gate procedure wraps a pamda and assigns it a plan (such as required, optional, sufficient, or requisite). The stack procedure then evaluates these gates in order.

For those migrating from traditional setups, Guile-PAM provides control-string->plan, which translates standard Linux-PAM control strings (e.g., success=ok ignore=ignore default=bad) directly into Guile-PAM plans.

Hybrid Stacks

Guile-PAM does not require a total abandonment of existing C-based modules. Using call-shared-object, a Guile-PAM stack can invoke legacy .so files. To prevent stability issues—since many legacy modules return PAM_SERVICE_ERR when called for actions they don't implement—Guile-PAM allows developers to specify exactly which actions a shared object implements using the #:implements keyword.

Advanced Use Cases: Beyond Simple Auth

Because Guile-PAM is an interpreted language with access to the system, it can achieve things that are difficult or impossible with standard PAM modules.

Example: User-Level Mounting

A primary motivation for Guile-PAM was the ability to mount volumes as the actual user rather than as root. While pam_mount.so exists, it typically mounts as root, which fails with FUSE folders or kerberized NFSv4.

Guile-PAM enables a workflow where a token received during pam_sm_authenticate is stored and then used during pam_sm_open_session to pipe a secret into a user-space tool like gocryptfs, mounting the home directory as the user during the login process.

Security and Performance Considerations

Moving authentication logic to an interpreted language introduces specific considerations:

Security

  • Readability: The author argues that Scheme code is generally easier to audit than C, increasing the likelihood of peer review.
  • Injection Risks: To prevent attacks, developers are cautioned against using Guile's read procedure on unknown inputs.
  • Environment Isolation: pam_guile.so isolates environment variables like GUILE_LOAD_PATH to prevent unauthorized module substitution.

Performance

Practical tests indicate that Guile-PAM performs at adequate speeds. Interestingly, the author notes that extreme speed is not always a goal in authentication, as intentional delays are often built-in to thwart brute-force attacks.

Implementation Caveats

Users should be aware that Guile-PAM is currently in the alpha stage. There are key behavioral differences compared to mature Linux-PAM:

  • Action Grouping: Linux-PAM groups certain actions (like auth and setcred) and may skip the second if the first returns PAM_IGNORE. Guile-PAM's (pam stack) implementation evaluates every module for every action.
  • Skip Counts: In legacy instruction sets with explicit skip counts, an unlocked Guile-PAM stack always acquires the result, whereas Linux-PAM's side effects vary by action.

Conclusion

Guile-PAM represents a shift toward more transparent and flexible system administration. By treating authentication as a programmable logic problem rather than a configuration of binary blobs, it opens the door to novel branching strategies and deeper integration with other free-software systems, such as OpenBSD scripts. For those using GNU Guix, the integration is even more seamless, allowing the entire authentication stack to be defined within the system configuration.

References

HN Stories