Add a pam_filter I/O Filter Without Breaking the Safety Boundary
pam_filter starts a separate program to filter a PAM application's input and output, and a careless setup quietly turns it into a privilege boundary. This guide configures it for one service, picks when it runs, and tests it without that trap. Allow about 20 minutes, plus a maintenance window if the service is a real login path. 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 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, read-only commands needing no 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
PAM loads the module as pam_filter.so; the configuration itself lives as a line in the chosen PAM service file. Start with a disposable or locally controlled service if you have one. 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
Swap in a real file name such as login for SERVICE. The sudo here is unavoidable, because PAM configuration controls authentication and sessions.
2. Install and inspect the filter executable
pam_filter does not hand you a useful general filter command line. The final argument is the full path of a filter executable, followed by any arguments it expects. Confirm 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
Security warning
Never point this at a writable directory, a script that accepts untrusted options, or a file inside a user's home directory. PAM runs the filter with the privilege of the calling application, not the privilege of the user, so a filter an ordinary user can replace becomes code execution at the application's privilege.
The installed package ships /usr/share/doc/libpam-modules/examples/upperLOWER.c as source for the case-transposing example this module's manpage documents. 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:
- session:
run1runs at session open,run2at session close. - auth:
run1ties to authentication,run2to credential setup. - password:
run1is the preliminary check,run2is the password update. - 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 just because the application is interactive, since that runs the program while credentials are actively being handled.
4. Add the narrowest PAM rule
Make a second copy of the file before editing, then add one rule matching the service's existing style. This is a template, not something to paste unchanged:
session required pam_filter.so run1 /absolute/path/to/filter FILTER_ARGUMENT
The control value required means the stack records a failure but keeps going through the rest of the stack, so 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, using a path that does not depend on the caller's current directory.
Leave the default terminal handling in place unless the filter needs it changed. 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 at all. These are deliberate choices, not harmless troubleshooting switches.
Warning
Editing a PAM file can lock you out or break a login service. Keep an existing root shell open, make one change at a time, 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: a second local login for a login service, or the application itself with a test account for a custom PAM application. Watch the service log and note whether the expected filter behaviour actually happens.
If you enabled debug, the module prints diagnostic information through the service's logging path. That does not make a broken executable safe, and it may expose details about a login flow, so remove debug after testing unless the service's logging policy explicitly allows 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 PAM can find an executable. They prove nothing about whether the filter can handle the application's byte stream or whether you picked the right PAM stage.
6. Undo a failed test safely
If the service misbehaves or a login attempt fails, use the still-open administrative shell to restore the backup right away:
$ 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'
Recovery
Use the exact backup you made for this change. If the service file has received unrelated edits since, stop and merge 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, then testing the service fresh, is the undo operation. Keep the filter executable in place until the PAM rule is gone and every test session has ended, and remove it only once 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 own.
- Choosing the wrong run stage.
session run2means session close, not a second interactive pass. - Assuming every application is supported. The module targets tty and standard-input/output applications; it is not a universal stream interceptor.
- Testing only the file syntax. PAM configuration can parse cleanly while the filter fails at runtime, so always test the real service path.
Done means
- Module confirmed: the installed
pam_filter.soand Linux-PAM package version are known. - Filter is trusted: it is a trusted, executable absolute path, owned and protected appropriately.
- Stage matches intent:
run1orrun2matches the intended PAM management stage. - Tested safely: the change was tested from a separate session with a recovery shell available.
- Rollback ready: a known-good backup can restore the PAM service configuration.
- Privilege understood: you know the filter runs with the calling application's privilege, not the user's.