CVE-2026-48064
Description
pam_usb provides hardware authentication for Linux using ordinary removable media. Prior to 0.9.1, when a PAM service is configured with deny_remote=false in pam_usb (commonly done for display managers such as gdm-password or lightdm to bypass process/TTY heuristics for local sessions), the PAM_RHOST check in pusb_do_auth() is also skipped. PAM_RHOST is set by remote daemons (sshd, XDMCP servers) to identify the remote client address. Because the check is gated inside if (opts.deny_remote), a genuine remote XDMCP connection reaches the USB device authentication step instead of being rejected. This vulnerability is fixed in 0.9.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
pam_usb 0.9.0 and earlier skips PAM_RHOST check when deny_remote=false, allowing remote XDMCP authentication with USB device.
Vulnerability
In pam_usb versions prior to 0.9.1, when a PAM service is configured with deny_remote=false (commonly for display managers like gdm-password or lightdm), the PAM_RHOST check is skipped, allowing remote authentication via XDMCP [1][2].
Exploitation
An attacker needs a cloned or stolen USB device and the victim's password. If XDMCP is enabled (non-default) and the display manager service is whitelisted, the attacker can authenticate remotely via XDMCP, bypassing locality enforcement [2].
Impact
Successful exploitation allows remote authentication over XDMCP, gaining access to the system with the victim's privileges. This undermines the hardware authentication mechanism [2].
Mitigation
Update to pam_usb 0.9.1, which removes the if (opts.deny_remote) guard from the PAM_RHOST check so it runs unconditionally [2]. No known workaround exists for unpatched versions.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Patches
1206fe0bc71ec[Fix] Make PAM_RHOST check unconditional — decouple from deny_remote (issue #348)
2 files changed · +31 −14
src/pam.c+11 −14 modified@@ -81,21 +81,18 @@ static int pusb_do_auth( return PAM_IGNORE; } - if (opts.deny_remote) + retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); + if (retval != PAM_SUCCESS) { - retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); - if (retval != PAM_SUCCESS) - { - log_error("Unable to retrieve PAM_RHOST.\n"); - pusb_conf_free(&opts); - return PAM_AUTH_ERR; - } - else if (rhost != NULL && *rhost != '\0') - { - log_debug("RHOST is set (%s), must be a remote request - disabling myself for this request!\n", rhost); - pusb_conf_free(&opts); - return PAM_IGNORE; - } + log_error("Unable to retrieve PAM_RHOST.\n"); + pusb_conf_free(&opts); + return PAM_AUTH_ERR; + } + if (rhost != NULL && *rhost != '\0') + { + log_debug("RHOST is set (%s), must be a remote request - disabling myself for this request!\n", rhost); + pusb_conf_free(&opts); + return PAM_IGNORE; } if (print_version)
tests/unit/c/pam_test.c+20 −0 modified@@ -157,6 +157,24 @@ static void test_auth_deny_remote_rhost_retrieval_fails(void **state) assert_int_equal(PAM_AUTH_ERR, pam_sm_authenticate(NULL, 0, 0, NULL)); } +static void test_auth_rhost_set_deny_remote_false_returns_ignore(void **state) +{ + (void)state; + reset_mocks(); + g_deny_remote = 0; + g_rhost = "192.168.1.1"; + assert_int_equal(PAM_IGNORE, pam_sm_authenticate(NULL, 0, 0, NULL)); +} + +static void test_auth_rhost_retrieval_fails_deny_remote_false(void **state) +{ + (void)state; + reset_mocks(); + g_deny_remote = 0; + g_rhost_retval = PAM_AUTH_ERR; + assert_int_equal(PAM_AUTH_ERR, pam_sm_authenticate(NULL, 0, 0, NULL)); +} + static void test_auth_local_login_denied(void **state) { (void)state; @@ -216,6 +234,8 @@ int main(void) cmocka_unit_test(test_auth_disabled_returns_ignore), cmocka_unit_test(test_auth_deny_remote_rhost_set_returns_ignore), cmocka_unit_test(test_auth_deny_remote_rhost_retrieval_fails), + cmocka_unit_test(test_auth_rhost_set_deny_remote_false_returns_ignore), + cmocka_unit_test(test_auth_rhost_retrieval_fails_deny_remote_false), cmocka_unit_test(test_auth_local_login_denied), cmocka_unit_test(test_auth_device_check_success), cmocka_unit_test(test_auth_device_check_failure),
Vulnerability mechanics
Root cause
"The PAM_RHOST check is incorrectly gated inside the `if (opts.deny_remote)` guard, so when `deny_remote=false` (whitelisted services), the remote host detection is skipped entirely."
Attack vector
An attacker must have network access to an XDMCP-enabled display manager (e.g., GDM, SDDM, LightDM) that is configured with `deny_remote=false` in pam_usb, and must possess a valid USB authentication device and the victim's password [ref_id=1][ref_id=2]. Because the PAM_RHOST check is gated inside `if (opts.deny_remote)`, a remote XDMCP connection reaches the USB device authentication step instead of being rejected [ref_id=2]. XDMCP is disabled by default on most distributions, so this requires a non-default configuration [ref_id=1].
Affected code
The vulnerability resides in `src/pam.c` within `pusb_do_auth()`, where the PAM_RHOST retrieval and comparison is wrapped inside `if (opts.deny_remote)` [ref_id=1][ref_id=2]. Additionally, in `src/local.c` lines 329–331, when `deny_remote=false`, `pusb_local_login()` returns 1 immediately, skipping all sub-checks including VNC/RDP/evdev detection [ref_id=1].
What the fix does
The fix removes the `if (opts.deny_remote)` guard from the PAM_RHOST block in `src/pam.c` so the check runs unconditionally [ref_id=2]. The `pusb_local_login()` call remains unconditional in `pam.c`; the early-return for `!deny_remote` stays inside `local.c` and continues to skip the heuristic checks as intended [ref_id=2]. This decouples the PAM_RHOST check from the `deny_remote` config option, ensuring that remote host detection via PAM_RHOST always occurs regardless of the service whitelist entry [ref_id=1].
Preconditions
- configThe target system must have XDMCP enabled on a display manager (GDM, SDDM, or LightDM) that is configured with deny_remote=false in pam_usb
- inputAttacker must possess a valid USB authentication device and the victim's password
- networkAttacker must have network access to the XDMCP service on the target
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.