Slot pam_selinux Into a PAM Session Stack Safely
pam_selinux sets the SELinux context on session open and restores it on close, and getting its stack position wrong is a classic way to break logins. The examples match the installed Linux-PAM package, version 1.5.3-5ubuntu5.7, whose module lives at /lib/x86_64-linux-gnu/security/pam_selinux.so.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Allow: 15 to 20 minutes.
- You need: root access, a second login path you've already tested, and an SELinux policy appropriate for the service.
- It will not: enable SELinux, install policy, or choose a policy for you. This is a session module only.
Safety boundary
A malformed PAM file can prevent logins, including administrative ones. Keep an existing root shell or console open while testing, and do not close your last known-good session until the new session has been verified.
1. Check the module and the service file
PAM reads service-specific files from /etc/pam.d/, one per service name (login, sshd, and so on). Start by checking the module and identifying the service you intend to change:
$ dpkg-query -W -f='${Package} ${Version}\n' libpam-modules:amd64
libpam-modules:amd64 1.5.3-5ubuntu5.7
$ test -r /lib/x86_64-linux-gnu/security/pam_selinux.so && echo 'pam_selinux is installed'
pam_selinux is installed
$ sudo sed -n '1,220p' /etc/pam.d/LOGIN_SERVICE
Replace LOGIN_SERVICE with a real service name such as login or sshd. The first command is ordinary user work; reading a root-owned service file may need sudo.
Checkpoint
Stop if the module is absent, if you have no recovery login, or if you can't identify which service starts the session you want to label. Installing packages or enabling SELinux is a separate change with its own rollback plan, not part of this one.
2. Understand what open and close do
On session open, the module prepares the execution context used by the next execve call, and also sets the context used for the controlling terminal and a new kernel keyring. On session close, it restores whatever contexts were in effect before it changed them. Use both phases for a complete session lifecycle:
session ... pam_selinux.so close
session ... pam_selinux.so open
The order is not cosmetic. The module documentation recommends placing open after any PAM modules that execute applications, and close before them, which avoids another module running with an unintended context. On a stock Ubuntu configuration, the SELinux close rule sits deliberately near the start of the session stack and open appears later, before the user-session work that follows it.
The module only provides the session type. Do not add it as an auth, account or password rule, it simply won't do anything useful there.
3. Back up the PAM file before editing
This is the one state-changing step in the guide. In the root shell you're keeping open, make a timestamped copy of the exact service file:
$ service=LOGIN_SERVICE
$ sudo cp --preserve=mode,ownership "/etc/pam.d/$service" "/etc/pam.d/$service.before-pam-selinux"
$ sudo ls -l "/etc/pam.d/$service" "/etc/pam.d/$service.before-pam-selinux"
Replace LOGIN_SERVICE before running this. The backup is a recovery file, not a second PAM service file that PAM will ever load, so keep it until the session test passes.
Recovery
To undo this guide after a failed edit, restore the backup from the still-open root shell:
$ sudo cp --preserve=mode,ownership "/etc/pam.d/$service.before-pam-selinux" "/etc/pam.d/$service"
Do not delete the backup while you're still testing. Restoring a PAM file is safer than trying to reconstruct several experimental lines from memory.
4. Add the two session rules
Edit only the target service file and preserve its existing control policy. A minimal entry can use optional, as shown in the installed manual's own example:
session optional pam_selinux.so close
session optional pam_selinux.so open
That's syntax, not a universal placement recommendation. In a real distribution file, copy the surrounding style and place the rules according to the session ordering above. Existing files may use a bracketed control value so a machine without a loaded SELinux module can ignore the rule while still treating other failures as errors, don't replace such a value with optional without understanding what you'd change.
If your service already has both lines, don't add duplicates. Check the whole file first:
$ sudo grep -n 'pam_selinux\.so' "/etc/pam.d/$service"
12:session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
31:session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
The line numbers above are illustrative; yours will differ. What matters is that the file ends up with one deliberate close rule and one deliberate open rule, not two of either.
5. Choose context input only when you need it
With no extra option, the module uses the default context selected by the SELinux policy, which is the least surprising starting point. Three mutually exclusive options ask for something different:
select_contextasks the user interactively for a custom role and, when MLS is enabled, an MLS level.env_paramsreadsSELINUX_ROLE_REQUESTEDandSELINUX_LEVEL_REQUESTEDfrom the PAM environment. SetSELINUX_USE_CURRENT_RANGEto1and it also requests the current range.use_current_rangeuses the current process sensitivity level and suppresses a separate sensitivity request.
Warning
Do not put select_context and env_params on the same line, the installed module reports that combination as invalid. Treat environment-controlled role selection as security-sensitive: only use it when the calling service controls that environment and your SELinux policy explicitly supports the requested transition.
6. Verify without locking yourself out
First inspect the final file and the module's presence, then start a new session through the changed service while keeping the old session open:
$ sudo grep -n 'pam_selinux\.so' "/etc/pam.d/$service"
$ ls -l /lib/x86_64-linux-gnu/security/pam_selinux.so
$ ssh USERNAME@HOSTNAME
Replace USERNAME and HOSTNAME with a test account and host, the SSH command is only an example. Use the actual service, and a second terminal or console rather than the shell that can still restore the file.
Inside the new session, check the process context if the SELinux tools are installed:
$ id -Z
USER:ROLE:TYPE:LEVEL
The exact context is policy- and machine-specific, don't expect that placeholder output literally. A successful login alone doesn't prove the intended domain transition happened, so compare the result with your policy's expected user context and check the service log if the session fails.
Recovery
Documented module failures include an invalid or unavailable context, an unknown user, and a session error, and a failed open can stop a session depending on the control value. If the test can't log in, use the still-open recovery shell to restore the backup, then confirm the service accepts sessions again before investigating policy further.
7. Use debug and verbose only while investigating
Add debug only for a controlled troubleshooting run, it sends module diagnostics through syslog. Add verbose when you need the module to try telling the user that a context was set:
session ... pam_selinux.so open debug verbose
Neither option repairs an SELinux policy or makes an invalid context valid. Remove them after the test: routine authentication and session logs shouldn't be any noisier than they need to be.
Done means
- Module confirmed. The installed module and package version were checked.
- Rules paired. The target PAM service has exactly one intentional
closerule and one intentionalopenrule. - Ordering respected. The rules are placed around other session modules that may execute applications.
- Control values preserved. Any existing bracketed control values stayed as they were unless their behaviour was deliberately reviewed.
- Backup verified before use. A backup remains available, and a new session was tested before the recovery shell was closed.
- Context options used correctly.
select_contextandenv_paramswere never combined, and custom context selection was used only with a matching SELinux policy.