Add a PAM I/O filter to a session without losing the safety boundary
You will configure pam_filter for one PAM service, select when its filter runs, and test the configuration without turning the filter into an accidental privilege boundary. Allow about 20 minutes, plus a maintenance window if the service is a real login path. The examples use Linux-PAM 1.5.3-5ubuntu5.7 from the installed libpam-modules:amd64 package.
The route
Jump straight to the step you need, or tick off Done means at the end.
This module is for tty-based or standard-input and standard-output applications. It starts a separate filter program for the input and output passing between the application and the user. It is not a general-purpose way to inspect network traffic or arbitrary application data.
1. Confirm the module and choose a test service
Check the package version and module path first. These are read-only commands and do not need elevated privileges:
$ dpkg-query -W -f='${Package} ${Version}\n' libpam-modules
libpam-modules 1.5.3-5ubuntu5.7
$ test -r /usr/lib/x86_64-linux-gnu/security/pam_filter.so && echo 'module is readable'
module is readable
The module is loaded by PAM as pam_filter.so, while its configuration is a line in the selected PAM service file. Start with a disposable or locally controlled service if one is available. Do not begin by editing /etc/pam.d/login or an SSH service on a remote-only machine.
Checkpoint: identify the service file you intend to change and save a root-owned backup before editing it:
$ sudo cp --preserve=mode,ownership,timestamps /etc/pam.d/SERVICE /etc/pam.d/SERVICE.before-pam-filter
Replace SERVICE with a real file name such as login. The sudo command requires elevated privileges because PAM configuration controls authentication and sessions.
2. Install and inspect the filter executable
pam_filter does not provide a useful general filter command line for you. The final argument is the full path of a filter executable, followed by any arguments that executable expects. Confirm that the file exists, is executable, and is owned by a trusted account:
$ FILTER='/absolute/path/to/filter'
$ test -x "$FILTER" && echo 'filter is executable'
$ stat -c '%A %U:%G %n' "$FILTER"
-rwxr-xr-x root:root /absolute/path/to/filter
Use the actual path in both commands. Do not point at a writable directory, a script that accepts untrusted options, or a file in a user's home directory. The PAM module runs the filter with the privilege of the calling application, not the privileges of the user. A filter that an ordinary user can replace can therefore become code execution at the application's privilege.
The installed package includes /usr/share/doc/libpam-modules/examples/upperLOWER.c as source for the case-transposing example documented by this module. That source file is not itself an executable filter. Do not put its path in a PAM rule and expect it to run.
3. Pick the PAM stage deliberately
The required run1 or run2 argument is not a filter mode. It selects which of PAM's two calls in a management group starts the filter:
- For
session,run1runs at session open andrun2at session close. - For
auth,run1is tied to authentication andrun2to credential setup. - For
password,run1is the preliminary check andrun2is the password update. - For
account, either value may be used.
For an interactive post-login filter, session with run1 is the least surprising starting point. Do not choose a password or authentication stage merely because the application is interactive: that can run the program while credentials are being handled.
4. Add the narrowest PAM rule
Make a second copy of the file if you are about to edit it, then add one rule with the service's existing style. This example is a template, not a command to paste unchanged:
session required pam_filter.so run1 /absolute/path/to/filter FILTER_ARGUMENT
The control value required means the PAM stack records a failure while continuing according to the rest of the stack; the service can still fail later because of that recorded error. Follow the existing service policy and the PAM documentation before choosing a different control value. Keep the filter path and arguments on the same line, and use a path that does not depend on the caller's current directory.
When the filter must see the original terminal identifier, leave the default terminal handling in place. Add new_term only when the filter needs PAM_TTY to describe the filtered pseudo-terminal. Add non_term only when the module must not set PAM_TTY. These options are mutually purposeful choices, not harmless troubleshooting switches.
Editing a PAM file can lock you out or break a login service. Keep an existing root shell open, make only one change, and test from a separate session before closing it. Do not restart a critical service as part of the first test.
5. Test the filter and capture the result
Use the service's normal, non-destructive test path. For a local login service, that means a second local login or a controlled terminal; for a custom PAM application, run that application with a test account. Watch the service log and record whether the expected filter behaviour occurs.
If you enabled debug, the module prints diagnostic information through the service's logging path. It does not make a broken executable safe, and its output may expose details about a login flow. Remove debug after testing unless the service's logging policy explicitly permits it.
Checkpoint: verify the configuration line and the executable without changing them:
$ sudo grep -nF 'pam_filter.so' /etc/pam.d/SERVICE
$ sudo test -x /absolute/path/to/filter && echo 'configured filter is executable'
configured filter is executable
A readable configuration line and a zero-status file test only prove that PAM can find an executable. They do not prove that the filter can handle the application's byte stream or that the selected PAM stage is the one you intended.
6. Undo a failed test safely
If the service behaves incorrectly or a login attempt fails, use the still-open administrative shell to restore the backup immediately:
$ sudo cp --preserve=mode,ownership,timestamps /etc/pam.d/SERVICE.before-pam-filter /etc/pam.d/SERVICE
$ sudo grep -nF 'pam_filter.so' /etc/pam.d/SERVICE || echo 'pam_filter rule removed'
Use the exact backup you created for this change. If the service file has received unrelated edits since then, stop and merge the change manually rather than overwriting newer policy. A failed module load can produce PAM_ABORT; treat that as an immediate configuration problem and restore the known-good file.
There is no separate disable switch for a rule. Removing or commenting out the line, followed by a fresh service test, is the undo operation. Keep the filter executable until the PAM rule has been removed and all test sessions have ended. Remove it later only if nothing else uses it.
Common traps
- Using a source file as the filter.
upperLOWER.cis example source, not a runnable program. - Running it as the user. PAM executes the filter with the calling application's privilege, which can be higher than the user's.
- Choosing the wrong run stage.
session run2means session close, not a second interactive pass. - Assuming every application is supported. The module is intended for tty and standard-input/output applications; it is not a universal stream interceptor.
- Testing only the file syntax. PAM configuration can parse while the filter fails at runtime, so test the real service path.
Done means
- The installed
pam_filter.soand Linux-PAM package version are known. - The filter is a trusted, executable absolute path, owned and protected appropriately.
run1orrun2matches the intended PAM management stage.- The change was tested from a separate session with a recovery shell available.
- A known-good backup can restore the PAM service configuration.
- You understand that the filter runs with the calling application's privilege.