CVE-2026-47269
Description
pam_usb provides hardware authentication for Linux using ordinary removable media. Prior to 0.9.0, pam_usb's deny_remote feature checks utmpx ut_addr_v6 to detect whether an authentication request originates from a remote session. The outer guard was if (utent->ut_addr_v6[0] != 0), which only tests the first 32-bit word of the 128-bit address field. IPv4-mapped IPv6 addresses (::ffff:x.x.x.x) store the IPv4 address in ut_addr_v6[3] with ut_addr_v6[0] == 0. On systems where the SSH daemon listens on :: (IPv6 wildcard) with AddressFamily any -- common on Ubuntu and Debian -- incoming IPv4 connections are recorded in utmpx as IPv4-mapped IPv6 addresses. The outer check evaluates to false, the remote-detection block is skipped entirely, and the session is treated as local. deny_remote=true does not block the authentication. An attacker with physical access to a registered USB device can authenticate over SSH on an affected system as if they were sitting at a local terminal, bypassing the deny_remote restriction. This vulnerability is fixed in 0.9.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
pam_usb's deny_remote fails to detect IPv4-mapped IPv6 addresses, allowing remote SSH authentication with a USB device bypassing remote restrictions.
## Vulnerability pam_usb prior to 0.9.0 implements the deny_remote feature by checking utent->ut_addr_v6[0] != 0 to detect remote sessions. This only inspects the first 32-bit word of the 128-bit IPv6 address field. IPv4-mapped IPv6 addresses (::ffff:x.x.x.x) have ut_addr_v6[0] == 0, causing the check to fail and the session to be classified as local [2]. Affected versions: all before 0.9.0.
Exploitation
An attacker with physical access to a registered USB device can authenticate over SSH on a system where the SSH daemon listens on :: (IPv6 wildcard) with AddressFamily any, common on Ubuntu and Debian. The attacker initiates an SSH connection, and the IPv4-mapped IPv6 address bypasses the outer guard, so deny_remote=true does not block authentication [2].
Impact
Successful exploitation allows the attacker to authenticate remotely using a USB device that should only work for local sessions. This bypasses the intended remote restriction, leading to unauthorized access to the system with the privileges of the authenticated user [2].
Mitigation
Fixed in version 0.9.0 by commit 804fe24, which checks all four words of ut_addr_v6 [1]. Users should upgrade to 0.9.0 or later. No workaround is available if upgrade is not possible, as the fix requires code change.
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.
Affected products
2Patches
1804fe24eae3dFix IPv6 outer guard bypass in local.c; add snprintf checks in device.c
2 files changed · +14 −7
src/device.c+11 −5 modified@@ -77,11 +77,17 @@ static int pusb_device_connected(t_pusb_options *opts, UDisksClient *udisks) g_object_unref(drive); if (retval) { - snprintf(opts->device.name, sizeof(opts->device.name), "%s", opts->device_list[currentDevice].name); - snprintf(opts->device.vendor, sizeof(opts->device.vendor), "%s", opts->device_list[currentDevice].vendor); - snprintf(opts->device.model, sizeof(opts->device.model), "%s", opts->device_list[currentDevice].model); - snprintf(opts->device.serial, sizeof(opts->device.serial), "%s", opts->device_list[currentDevice].serial); - snprintf(opts->device.volume_uuid, sizeof(opts->device.volume_uuid), "%s", opts->device_list[currentDevice].volume_uuid); + int trunc = 0; + trunc |= snprintf(opts->device.name, sizeof(opts->device.name), "%s", opts->device_list[currentDevice].name) >= (int)sizeof(opts->device.name); + trunc |= snprintf(opts->device.vendor, sizeof(opts->device.vendor), "%s", opts->device_list[currentDevice].vendor) >= (int)sizeof(opts->device.vendor); + trunc |= snprintf(opts->device.model, sizeof(opts->device.model), "%s", opts->device_list[currentDevice].model) >= (int)sizeof(opts->device.model); + trunc |= snprintf(opts->device.serial, sizeof(opts->device.serial), "%s", opts->device_list[currentDevice].serial) >= (int)sizeof(opts->device.serial); + trunc |= snprintf(opts->device.volume_uuid, sizeof(opts->device.volume_uuid), "%s", opts->device_list[currentDevice].volume_uuid) >= (int)sizeof(opts->device.volume_uuid); + if (trunc) + { + log_error("Device field truncated (should not happen).\n"); + retval = 0; + } currentDevice = opts->device_count; break; }
src/local.c+3 −2 modified@@ -97,9 +97,10 @@ int pusb_is_tty_local(char *tty) } /** - * Note: despite the property name this also works for IPv4, v4 addr would be in ut_addr_v6[0] solely while for v6 it will have just a part of the ip. Anyway: if first element is set -> remote + * Note: despite the property name this also works for IPv4, v4 addr would be in ut_addr_v6[0] solely while for v6 it will have just a part of the ip. All four words are checked so that IPv6 addresses with a zero first word (e.g. ::ffff:x.x.x.x mapped addresses) are not incorrectly treated as local. **/ - if (utent->ut_addr_v6[0] != 0) { + if (utent->ut_addr_v6[0] || utent->ut_addr_v6[1] || + utent->ut_addr_v6[2] || utent->ut_addr_v6[3]) { char ipbuf[INET6_ADDRSTRLEN]; const char *ipaddr; if (utent->ut_addr_v6[1] || utent->ut_addr_v6[2] || utent->ut_addr_v6[3]) {
Vulnerability mechanics
Root cause
"Incomplete IPv6 address check in `pusb_is_tty_local` — only the first 32-bit word of `ut_addr_v6` is tested, so IPv4-mapped IPv6 addresses (::ffff:x.x.x.x) with a zero first word bypass remote session detection."
Attack vector
An attacker with physical access to a registered USB device can authenticate over SSH on a system where `deny_remote=true` is configured. The bug is that `pusb_is_tty_local` only checks `ut_addr_v6[0]` to detect remote sessions. On systems where the SSH daemon listens on `::` (IPv6 wildcard) with `AddressFamily any` — common on Ubuntu and Debian — incoming IPv4 connections are recorded in utmpx as IPv4-mapped IPv6 addresses (`::ffff:x.x.x.x`). These addresses have `ut_addr_v6[0] == 0`, so the remote-detection block is skipped entirely and the session is treated as local [ref_id=1]. The attacker needs network access to SSH and possession of a registered USB device, but no prior authentication.
Affected code
The vulnerable code is in `src/local.c` in the `pusb_is_tty_local` function. The outer guard `if (utent->ut_addr_v6[0] != 0)` only checks the first 32-bit word of the 128-bit `ut_addr_v6` field, missing addresses where that word is zero. The patch also defensively hardens `src/device.c` in `pusb_device_connected` by adding truncation checks to five `snprintf` calls, though that change is not related to the security bypass.
What the fix does
The fix in `src/local.c` changes the outer guard from `if (utent->ut_addr_v6[0] != 0)` to `if (utent->ut_addr_v6[0] || utent->ut_addr_v6[1] || utent->ut_addr_v6[2] || utent->ut_addr_v6[3])`, checking all four 32-bit words of the address field [patch_id=2779107]. This ensures that IPv4-mapped IPv6 addresses (`::ffff:x.x.x.x`), which have non-zero values in `ut_addr_v6[2]` and `ut_addr_v6[3]`, correctly trigger the remote-detection path. The changes in `src/device.c` add truncation checks to five `snprintf` calls as defensive hardening but are not required to close the security vulnerability.
Preconditions
- configpam_usb must be configured with deny_remote=true
- configSSH daemon must listen on IPv6 wildcard (::) with AddressFamily any (common on Ubuntu/Debian)
- inputAttacker must have physical access to a USB device registered with pam_usb
- networkAttacker must have network access to SSH on the target system
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.