CVE-2026-44710
Description
pam_usb provides hardware authentication for Linux using ordinary removable media. Prior to 0.8.7, src/device.c passed the return values of udisks_drive_get_serial(), udisks_drive_get_vendor(), and udisks_drive_get_model() directly to strcmp() without NULL checks. The GIO/UDisks API documentation states these accessors can return NULL for devices that do not expose the corresponding field. Passing NULL to strcmp() is undefined behaviour (typically a SIGSEGV). This vulnerability is fixed in 0.8.7.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
pam_usb <= 0.8.6 passes NULL returns from UDisks accessors to strcmp(), causing PAM crash and denial of service.
Vulnerability
In src/device.c, the return values of udisks_drive_get_serial(), udisks_drive_get_vendor(), and udisks_drive_get_model() are passed directly to strcmp() without NULL checks. The GIO/UDisks API documentation states these accessors can return NULL for devices that do not expose the corresponding field. Passing NULL to strcmp() is undefined behavior, typically causing a SIGSEGV. This affects pam_usb versions prior to 0.8.7. [1]
Exploitation
An attacker with physical access can plug in a USB device (or mass-storage gadget) that exposes no serial number via UDisks. When pam_usb tries to authenticate, it will attempt to compare the NULL serial with stored values, leading to a crash of the PAM module. The crash occurs during device enumeration, causing authentication to fail for all users on the affected service until the device is removed. [1]
Impact
A successful attack results in denial of service: authentication failures for all users on the service using pam_usb. On a single-user workstation configured with pam_usb as the sole authentication method, this can lead to a complete lockout. The attacker does not gain any privileges; the vulnerability only allows disruption of authentication. [1]
Mitigation
The vulnerability is fixed in pam_usb version 0.8.7. Users should upgrade to 0.8.7 or later. No workaround is available; removing the USB device stops the crash but does not prevent recurrence. [1]
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
2014042451850[Security] Fix multiple advisories of various crititicallity - UPDATE ASAP!
8 files changed · +170 −39
src/conf.c+1 −1 modified@@ -216,7 +216,7 @@ int pusb_conf_parse( continue; } - strcpy(opts->device_list[currentDevice].name, device_list[currentDevice]); + snprintf(opts->device_list[currentDevice].name, sizeof(opts->device_list[currentDevice].name), "%s", device_list[currentDevice]); pusb_conf_parse_device(opts, doc, currentDevice, device_list[currentDevice]); }
src/device.c+15 −3 modified@@ -50,16 +50,28 @@ static int pusb_device_connected(t_pusb_options *opts, UDisksClient *udisks) if (udisks_object_peek_drive(object)) { drive = udisks_object_get_drive(object); - retval = strcmp(udisks_drive_get_serial(drive), opts->device_list[currentDevice].serial) == 0; + + const gchar *serial = udisks_drive_get_serial(drive); + if (!serial) + { + log_debug("Drive has no serial number, skipping.\n"); + g_object_unref(drive); + continue; + } + retval = strcmp(serial, opts->device_list[currentDevice].serial) == 0; if (strcmp(opts->device_list[currentDevice].vendor, "Generic") != 0) { - retval = retval && strcmp(udisks_drive_get_vendor(drive), opts->device_list[currentDevice].vendor) == 0; + const gchar *vendor = udisks_drive_get_vendor(drive); + retval = retval && vendor != NULL && + strcmp(vendor, opts->device_list[currentDevice].vendor) == 0; } if (strcmp(opts->device_list[currentDevice].model, "Generic") != 0) { - retval = retval && strcmp(udisks_drive_get_model(drive), opts->device_list[currentDevice].model) == 0; + const gchar *model = udisks_drive_get_model(drive); + retval = retval && model != NULL && + strcmp(model, opts->device_list[currentDevice].model) == 0; } g_object_unref(drive);
src/pad.c+52 −20 modified@@ -43,7 +43,7 @@ static int pusb_pad_build_device_path( struct stat sb; snprintf(path_devpad, sizeof(path_devpad), "%s/%s", mnt_point, opts->device_pad_directory); - if (stat(path_devpad, &sb) != 0) + if (lstat(path_devpad, &sb) != 0) { log_debug("Directory %s does not exist, creating it.\n", path_devpad); if (mkdir(path_devpad, S_IRUSR | S_IWUSR | S_IXUSR) != 0) @@ -52,6 +52,11 @@ static int pusb_pad_build_device_path( return 0; } } + else if (S_ISLNK(sb.st_mode)) + { + log_error("Device pad directory %s is a symlink, refusing to use it.\n", path_devpad); + return 0; + } snprintf( path_out, @@ -91,7 +96,7 @@ static int pusb_pad_build_system_path( user_ent->pw_dir, opts->system_pad_directory ); - if (stat(dir_path, &sb) != 0) + if (lstat(dir_path, &sb) != 0) { log_debug("Directory %s does not exist, creating one.\n", dir_path); if (mkdir(dir_path, S_IRUSR | S_IWUSR | S_IXUSR) != 0) @@ -107,6 +112,11 @@ static int pusb_pad_build_system_path( chmod(dir_path, S_IRUSR | S_IWUSR | S_IXUSR); } + else if (S_ISLNK(sb.st_mode)) + { + log_error("System pad directory %s is a symlink, refusing to use it.\n", dir_path); + return 0; + } /* change slashes in device name to underscores */ snprintf(device_name, sizeof(device_name), "%s", opts->device.name); while (*device_name_ptr) @@ -137,19 +147,26 @@ static FILE *pusb_pad_open_device( const char *mode ) { - FILE *f; char path[1024*5]; + int flags = (mode[0] == 'r') ? O_RDONLY : (O_WRONLY | O_CREAT | O_TRUNC); if (!pusb_pad_build_device_path(opts, mnt_point, user, path, sizeof(path))) { return NULL; } - f = fopen(path, mode); - if (!f) + int fd = open(path, flags | O_NOFOLLOW, 0600); + if (fd < 0) { log_debug("Cannot open device file: %s\n", strerror(errno)); return NULL; } + FILE *f = fdopen(fd, mode); + if (!f) + { + close(fd); + log_debug("Cannot fdopen device file: %s\n", strerror(errno)); + return NULL; + } return f; } @@ -159,19 +176,26 @@ static FILE *pusb_pad_open_system( const char *mode ) { - FILE *f; char path[1024*5]; + int flags = (mode[0] == 'r') ? O_RDONLY : (O_WRONLY | O_CREAT | O_TRUNC); if (!pusb_pad_build_system_path(opts, user, path, sizeof(path))) { return NULL; } - f = fopen(path, mode); - if (!f) + int fd = open(path, flags | O_NOFOLLOW, 0600); + if (fd < 0) { log_debug("Cannot open system file: %s\n", strerror(errno)); return NULL; } + FILE *f = fdopen(fd, mode); + if (!f) + { + close(fd); + log_debug("Cannot fdopen system file: %s\n", strerror(errno)); + return NULL; + } return f; } @@ -295,19 +319,27 @@ static int pusb_pad_update( generateRandom(magic, sizeof(magic)); - if (!(f_device = fopen(path_device_tmp, "w"))) { - log_error("Unable to create temp device pad: %s\n", strerror(errno)); - return 0; + int fd_dev = open(path_device_tmp, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600); + if (fd_dev < 0 || !(f_device = fdopen(fd_dev, "w"))) + { + if (fd_dev >= 0) close(fd_dev); + log_error("Unable to create temp device pad: %s\n", strerror(errno)); + return 0; + } } pusb_pad_protect(user, fileno(f_device)); - if (!(f_system = fopen(path_system_tmp, "w"))) { - log_error("Unable to create temp system pad: %s\n", strerror(errno)); - fclose(f_device); - unlink(path_device_tmp); - return 0; + int fd_sys = open(path_system_tmp, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600); + if (fd_sys < 0 || !(f_system = fdopen(fd_sys, "w"))) + { + if (fd_sys >= 0) close(fd_sys); + log_error("Unable to create temp system pad: %s\n", strerror(errno)); + fclose(f_device); + unlink(path_device_tmp); + return 0; + } } pusb_pad_protect(user, fileno(f_system)); @@ -403,19 +435,19 @@ static int pusb_pad_compare( } log_debug("Loading device pad...\n"); bytes_read = fread(magic_device, sizeof(char), sizeof(magic_device), f_device); - if (!bytes_read) + if (bytes_read != sizeof(magic_device)) { - log_error("Can't read device pad!\n"); + log_error("Device pad is incomplete (%zu/%zu bytes).\n", bytes_read, sizeof(magic_device)); fclose(f_system); fclose(f_device); return 0; } log_debug("Loading system pad...\n"); bytes_read = fread(magic_system, sizeof(char), sizeof(magic_system), f_system); - if (!bytes_read) + if (bytes_read != sizeof(magic_system)) { - log_error("Can't read system pad!\n"); + log_error("System pad is incomplete (%zu/%zu bytes).\n", bytes_read, sizeof(magic_system)); fclose(f_system); fclose(f_device); return 0;
src/tmux.c+63 −5 modified@@ -18,11 +18,52 @@ #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <ctype.h> #include <regex.h> #include "log.h" #include "process.h" #include "mem.h" +/* Reject characters that could break out of shell double-quotes or inject commands. */ +static int pusb_tmux_is_safe_socket_path(const char *path) +{ + if (!path || *path == '\0') return 0; + for (const char *p = path; *p; p++) { + switch (*p) { + case '"': case '\'': case '`': case '$': case '\\': + case '\n': case '\r': case '\t': case '!': case ';': + case '&': case '|': case '<': case '>': case '(': + case ')': case '{': case '}': + return 0; + } + } + return 1; +} + +/* Client ID from TMUX must be purely numeric. */ +static int pusb_tmux_is_numeric_id(const char *s) +{ + if (!s || *s == '\0') return 0; + for (; *s; s++) { + if (!isdigit((unsigned char)*s)) return 0; + } + return 1; +} + +/* Escape ERE metacharacters in username so it matches literally in the regex. */ +static void pusb_tmux_escape_for_regex(const char *src, char *dst, size_t dstlen) +{ + const char *metachar = "\\.^$*+?{}[]|()"; + size_t j = 0; + for (size_t i = 0; src[i] && j + 2 < dstlen; i++) { + if (strchr(metachar, (int)src[i])) { + dst[j++] = '\\'; + } + dst[j++] = src[i]; + } + dst[j] = '\0'; +} + char *pusb_tmux_get_client_tty(pid_t env_pid) { char *tmux_details = getenv("TMUX"); @@ -49,6 +90,18 @@ char *pusb_tmux_get_client_tty(pid_t env_pid) char *tmux_socket_path = strtok(tmux_details, ","); log_debug(" Got tmux_socket_path: %s\n", tmux_socket_path); + if (!pusb_tmux_is_safe_socket_path(tmux_socket_path)) + { + log_error("TMUX socket path contains invalid characters, denying.\n"); + return NULL; + } + + if (!pusb_tmux_is_numeric_id(tmux_client_id)) + { + log_error("TMUX client ID is not numeric, denying.\n"); + return NULL; + } + size_t get_tmux_session_details_cmd_len = strlen(tmux_socket_path) + strlen(tmux_client_id) + 64; char *get_tmux_session_details_cmd = xmalloc(get_tmux_session_details_cmd_len); snprintf(get_tmux_session_details_cmd, get_tmux_session_details_cmd_len, "LC_ALL=C; tmux -S \"%s\" list-clients -t \"\\$%s\"", tmux_socket_path, tmux_client_id); @@ -82,8 +135,9 @@ char *pusb_tmux_get_client_tty(pid_t env_pid) log_debug(" Closing pipe for 'tmux list-clients' failed, this is quite a wtf...\n"); } - char *result = xmalloc(strlen(tmux_client_tty) + 1); - strcpy(result, tmux_client_tty); + size_t tty_len = strlen(tmux_client_tty); + char *result = xmalloc(tty_len + 1); + memcpy(result, tmux_client_tty, tty_len + 1); return result; } else @@ -107,24 +161,28 @@ int pusb_tmux_has_remote_clients(const char* username) char regex_raw[BUFSIZ]; char buf[BUFSIZ]; char msgbuf[100]; - char regex_tpl[2][BUFSIZ] = { + const char *regex_tpl[2] = { "(.+)([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})(.+)tmux(.+)", //v4 "(.+)([0-9A-Fa-f]{1,4}):([0-9A-Fa-f]{1,4}):([0-9A-Fa-f]{1,4}):([0-9A-Fa-f]{1,4})(.+)tmux(.+)" // v6 }; // ... yes, these allow invalid addresses. No, I don't care. This isn't about validation but detecting remote access. Good enough ¯\_(ツ)_/¯ + /* Max Linux username is 32 chars; doubled for escaping + null terminator = 65 bytes. */ + char escaped_username[65] = {0}; + pusb_tmux_escape_for_regex(username, escaped_username, sizeof(escaped_username)); + for (int i = 0; i <= 1; i++) { log_debug(" Checking for IPv%d connections...\n", (4 + (i * 2))); - if ((fp = popen("LC_ALL=C; w", "r")) == NULL) + if ((fp = popen("LC_ALL=C; /usr/bin/w", "r")) == NULL) { log_error("tmux detected, but couldn't get `w`. Denying since remote check for tmux impossible without it!\n"); return -1; } while (fgets(buf, BUFSIZ, fp) != NULL) { - snprintf(regex_raw, BUFSIZ, "%s%s", username, regex_tpl[i]); + snprintf(regex_raw, BUFSIZ, "%s%s", escaped_username, regex_tpl[i]); status = regcomp(®ex, regex_raw, REG_EXTENDED); if (status)
tools/pamusb-agent+14 −4 modified@@ -194,13 +194,22 @@ def userDeviceThread(user): else: logger.error('Ignoring empty command for user "%s".' % userName) + _DANGEROUS_ENV_VARS = { + 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'LD_AUDIT', 'LD_DEBUG', + 'GLIBC_TUNABLES', 'PYTHONPATH', 'PERL5LIB', 'RUBYLIB', + 'IFS', 'CDPATH', 'ENV', 'BASH_ENV', + } + for henv in hotplug.findall('env'): if henv.text is not None: henv_var = re.sub(r'^(.*?)=.*$', '\\1', henv.text) henv_arg = re.sub(r'^.*?=(.*)$', '\\1', henv.text) if henv_var != '' and henv_arg != '': - henvs[henv_var] = henv_arg + if henv_var in _DANGEROUS_ENV_VARS: + logger.error('Rejecting dangerous env var "%s" for user "%s".' % (henv_var, userName)) + else: + henvs[henv_var] = henv_arg else: logger.error('Ignoring invalid command environment variable for user "%s".' % userName) else: @@ -269,9 +278,10 @@ def userDeviceThread(user): logger.info('Device "%s" has been inserted. Performing verification...' % deviceName) - cmdLine = "%s --debug --config=%s --service=pamusb-agent %s" % (options['check'], options['configFile'], userName) - logger.info('Executing "%s"' % cmdLine) - if not os.system(cmdLine): + checkArgs = [options['check'], '--debug', '--config=' + options['configFile'], '--service=pamusb-agent', userName] + logger.info('Executing "%s"' % ' '.join(checkArgs)) + checkResult = subprocess.run(checkArgs, capture_output=True) + if checkResult.returncode == 0: logger.info('Authentication succeeded. Unlocking user "%s"...' % userName) for l in events['unlock']:
tools/pamusb-conf+11 −1 modified@@ -17,6 +17,7 @@ import sys import os +import re import gi import subprocess import socket @@ -352,14 +353,23 @@ def resetPads(): if user.getAttribute('id') == options['resetPads']: # Device specific pad for user userDevice = user.getElementsByTagName('device')[0].firstChild.nodeValue + if '/' in userDevice or '..' in userDevice: + print('Invalid device ID in config (path traversal): %s' % userDevice) + sys.exit(1) padFiles.append('/home/%s/.pamusb/%s.pad' % (options['resetPads'], userDevice)) # User specific pad for device for details in devicesObj: if details.getAttribute('id') == userDevice: deviceUUID = details.getElementsByTagName('volume_uuid')[0].firstChild.nodeValue + if not re.match(r'^[0-9a-fA-F\-]+$', deviceUUID): + print('Invalid device UUID in config: %s' % deviceUUID) + sys.exit(1) try: - deviceMountPoint = subprocess.check_output(['mount | grep `readlink -f /dev/disk/by-uuid/%s` | awk \'{print $3}\'' % (deviceUUID)], shell=True) + deviceMountPoint = subprocess.check_output( + ['findmnt', '-n', '-o', 'TARGET', '--source', '/dev/disk/by-uuid/' + deviceUUID], + stderr=subprocess.DEVNULL + ) padFiles.append('%s/.pamusb/%s.%s.pad' % (deviceMountPoint.decode().strip(), options['resetPads'], socket.gethostname())) except subprocess.CalledProcessError as err: print('Could not get device mountpoint: ', err)
tools/pamusb-keyring-unlock-gnome+8 −4 modified@@ -39,11 +39,15 @@ else logger -p local0.notice -t ${0##*/}[$$] Killed existing gnome-keyring-instance. fi -# Read UNLOCK_PASSWORD from $KEYFILE -. ~/.pamusb/.keyring_unlock_password +# Read UNLOCK_PASSWORD from $KEYFILE via grep to avoid sourcing arbitrary shell +UNLOCK_PASSWORD=$(grep -oP '^UNLOCK_PASSWORD=\K.*' ~/.pamusb/.keyring_unlock_password) +if [ -z "$UNLOCK_PASSWORD" ]; then + logger -p local0.error -t ${0##*/}[$$] Could not read UNLOCK_PASSWORD from file. + exit 1 +fi -# Perform unlock -echo -n $UNLOCK_PASSWORD | gnome-keyring-daemon --daemonize --login \ +# Perform unlock — pass via stdin pipe so the password never appears as a process argument +printf '%s' "$UNLOCK_PASSWORD" | gnome-keyring-daemon --daemonize --login \ && gnome-keyring-daemon --start > /dev/null 2>&1; UNLOCKED=$? \ && logger -p local0.notice -t ${0##*/}[$$] Keyring unlocked. \ || logger -p local0.error -t ${0##*/}[$$] Keyring unlock failed.
tools/pamusb-pinentry+6 −1 modified@@ -16,6 +16,7 @@ # Street, Fifth Floor, Boston, MA 02110-1301 USA. import os +import sys import subprocess import getpass from dotenv import load_dotenv @@ -35,4 +36,8 @@ if (isAuthenticated.returncode == 0): exit() print("OK") else: - subprocess.run(fallbackPinentryApp) + if fallbackPinentryApp and os.path.isabs(fallbackPinentryApp) and os.path.isfile(fallbackPinentryApp) and os.access(fallbackPinentryApp, os.X_OK): + subprocess.run([fallbackPinentryApp]) + else: + sys.stderr.write('PINENTRY_FALLBACK_APP is not set or not a valid executable path, cannot fall back.\n') + sys.exit(1)
987f4f6ddf0aPrepare v0.8.7
7 files changed · +46 −7
arch_linux/PKGBUILD_git+1 −1 modified@@ -2,7 +2,7 @@ # Contributor: Pekka Helenius <fincer89 [at] hotmail [dot] com> pkgname=pam_usb-git -pkgver=0.8.6_r589.g370655a +pkgver=0.8.7_r596.g9bce707 pkgrel=1 pkgdesc='Hardware authentication for Linux using ordinary flash media (USB & Card based).' arch=($CARCH)
arch_linux/PKGBUILD_stable+1 −1 modified@@ -2,7 +2,7 @@ # Contributor: Pekka Helenius <fincer89 [at] hotmail [dot] com> pkgname=pam_usb -pkgver=0.8.6 +pkgver=0.8.7 pkgrel=1 pkgdesc='Hardware authentication for Linux using ordinary flash media (USB & Card based).' arch=($CARCH)
ChangeLog+10 −0 modified@@ -1,3 +1,13 @@ +* 0.8.7 + [Enhancement] Specify a dedicated device for superuser services + [Enhancement] Restore PolicyKit support (also for 127) + [Enhancement] Remove default installation of pamusb-pinentry + [Security] Fixed GHSA-822m-whrh-vrj8 + [Security] Fixed GHSA-jgv5-w6rm-7wxg + [Security] Fixed GHSA-fjpm-p9pj-mp34 + [Security] Fixed GHSA-j8cq-2gv6-gfwf + [Security] Fixed GHSA-jxrj-q67x-wr4c + * 0.8.6 [Enhancement] Documentation updates [Enhancement] Remote VSCode tunnels are now detected in deny_remote check (thx @jaoppb)
debian/changelog+12 −0 modified@@ -1,3 +1,15 @@ +libpam-usb (0.8.7) unstable; urgency=critical + * [Enhancement] Specify a dedicated device for superuser services + * [Enhancement] Restore PolicyKit support (also for 127) + * [Enhancement] Remove default installation of pamusb-pinentry + * [Security] Fixed GHSA-822m-whrh-vrj8 + * [Security] Fixed GHSA-jgv5-w6rm-7wxg + * [Security] Fixed GHSA-fjpm-p9pj-mp34 + * [Security] Fixed GHSA-j8cq-2gv6-gfwf + * [Security] Fixed GHSA-jxrj-q67x-wr4c + + -- Tobias Bäumer <tobiasbaeumer@gmail.com> Tue, 05 May 2026 21:00:00 +0200 + libpam-usb (0.8.6) unstable; urgency=medium * [Enhancement] Documentation updates * [Enhancement] Remote VSCode tunnels are now detected in deny_remote check (thx @jaoppb)
fedora/SPECS/pam_usb.spec+12 −1 modified@@ -1,7 +1,7 @@ %define _topdir /usr/local/src/pam_usb/fedora %define name pam_usb %define release 1 -%define version 0.8.6 +%define version 0.8.7 %define buildroot %{_topdir}/%{name}‑%{version}‑root BuildRoot: %{buildroot} @@ -61,6 +61,17 @@ rm -rf %{buildroot}/usr/share/pam-configs %doc %attr(0644,root,root) /usr/share/doc/pam_usb/TROUBLESHOOTING %changelog + +* Tue May 05 2026 Tobias Bäumer <tobiasbaeumer@gmail.com> - 0.8.7 +- [Enhancement] Specify a dedicated device for superuser services +- [Enhancement] Restore PolicyKit support (also for 127) +- [Enhancement] Remove default installation of pamusb-pinentry +- [Security] Fixed GHSA-822m-whrh-vrj8 +- [Security] Fixed GHSA-jgv5-w6rm-7wxg +- [Security] Fixed GHSA-fjpm-p9pj-mp34 +- [Security] Fixed GHSA-j8cq-2gv6-gfwf +- [Security] Fixed GHSA-jxrj-q67x-wr4c + * Fri May 03 2026 Tobias Bäumer <tobiasbaeumer@gmail.com> - 0.8.6 - [Enhancement] Documentation updates - [Enhancement] Remote VSCode tunnels are now detected in deny_remote check (thx @jaoppb)
SECURITY.md+9 −3 modified@@ -5,10 +5,16 @@ At every point in time only the most recent release is supported. | Version | Supported | -| ------- | ------------------ | -| 0.8.6 | :white_check_mark: | -| < 0.8.6 | :x: | +|---------| ------------------ | +| 0.8.7 | :white_check_mark: | +| < 0.8.7 | :x: | ## Reporting a Vulnerability Please email me at tobiasbaeumer@gmail.com, since Github issues are public. + +## Note on versions <= 0.8.6 + +There are multiple not yet published security advisories for these versions. Details will follow. + +But if you're running anything except 0.8.7 please update immediately!
src/version.h+1 −1 modified@@ -18,6 +18,6 @@ #ifndef PUSB_VERSION_H_ # define PUSB_VERSION_H_ -# define PUSB_VERSION "0.8.6" +# define PUSB_VERSION "0.8.7" #endif /* !PUSB_VERSION_H_ */
Vulnerability mechanics
Root cause
"Missing NULL checks on return values of udisks_drive_get_serial(), udisks_drive_get_vendor(), and udisks_drive_get_model() before passing them to strcmp()."
Attack vector
An attacker with physical access plugs in a USB device or mass-storage gadget that does not expose a serial number, vendor, or model string via UDisks. When the PAM module enumerates devices during authentication, the NULL pointer returned by the UDisks accessor is passed to `strcmp()`, causing undefined behavior — typically a SIGSEGV crash. This crashes the PAM module, denying authentication to all users on the affected service until the offending device is removed [ref_id=1].
Affected code
The vulnerability is in `src/device.c` in the function `pusb_device_check()`. The return values of `udisks_drive_get_serial()`, `udisks_drive_get_vendor()`, and `udisks_drive_get_model()` are passed directly to `strcmp()` without NULL checks [ref_id=1].
What the fix does
The fix, committed in d9acb56, adds NULL checks before all three `strcmp()` calls in `pusb_device_check()`. When a NULL serial, vendor, or model is returned, the drive is skipped (treated as non-matching) rather than being passed to `strcmp()`. This prevents the undefined behavior and crash [ref_id=1].
Preconditions
- networkAttacker must have physical access to the target machine.
- configThe target must be running pam_usb version 0.8.6 or earlier.
- inputThe attacker must plug in a USB device that does not expose a serial number, vendor, or model via UDisks.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.