VYPR
Medium severity4.6NVD Advisory· Published May 27, 2026

CVE-2026-44710

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

2
  • Mcdope/Pam Usbinferred2 versions
    <0.8.7+ 1 more
    • (no CPE)range: <0.8.7
    • (no CPE)range: <0.8.7

Patches

2
014042451850

[Security] Fix multiple advisories of various crititicallity - UPDATE ASAP!

https://github.com/mcdope/pam_usbMcDopeMay 4, 2026Fixed in 0.8.7via llm-release-walk
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(&regex, 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)
    
987f4f6ddf0a

Prepare v0.8.7

https://github.com/mcdope/pam_usbMcDopeMay 5, 2026Fixed in 0.8.7via release-tag
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

1

News mentions

0

No linked articles in our index yet.