VYPR
Medium severity6.5NVD Advisory· Published May 27, 2026

CVE-2026-47273

CVE-2026-47273

Description

pam_usb provides hardware authentication for Linux using ordinary removable media. Prior to 0.9.0, pam_usb builds XPath expressions from user-supplied identifiers (PAM username, service name) and device-supplied identifiers (USB device serial, model, vendor) to query /etc/pamusb.conf. These identifiers were not validated for XPath metacharacters, allowing injection of arbitrary XPath predicates. This vulnerability is fixed in 0.9.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

pam_usb before 0.9.0 builds XPath queries from unvalidated user/device identifiers, allowing authentication bypass via XPath injection.

## Vulnerability pam_usb versions prior to 0.9.0 construct XPath expressions using user-supplied identifiers (PAM username, service name) and device-supplied identifiers (USB device serial, model, vendor) to query /etc/pamusb.conf. These identifiers were not sanitized for XPath metacharacters such as single quotes. An attacker can inject arbitrary XPath predicates by providing a crafted username (e.g., via SSH) or a USB device with a malicious serial number, altering the intended configuration lookup path [1][2][3].

Exploitation

An attacker needs the ability to control a PAM username (e.g., during login via SSH) or to present a USB device with a crafted serial/model/vendor string to the system. The XPath query of the form //user[@id='USERNAME']/device[@id='DEVICE_ID'] is built directly from these inputs. By injecting a single quote and additional XPath predicates, the attacker can change the matched device entry, potentially authenticating with a different user's registered device or bypassing device checks entirely [2][3].

Impact

Successful exploitation allows an attacker to bypass hardware authentication requirements, potentially gaining access to a target system using a USB device not belonging to them or by subverting device validation logic. The impact is authentication bypass, which could lead to unauthorized access or privilege escalation depending on the PAM service configuration [1][2].

Mitigation

The vulnerability is fixed in pam_usb version 0.9.0. The fix, introduced in commit 721fed0, adds a pusb_conf_xpath_id_is_safe() validator that rejects identifiers containing single quotes or control characters (0x00–0x1F) before they are used in XPath expressions [1][2][3]. Users should upgrade to 0.9.0 or later. No workarounds are available for earlier 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

1
721fed08a359

Reject unsafe config XPath ids

https://github.com/mcdope/pam_usbnicMay 14, 2026via body-scan-shorthand
3 files changed · +97 11
  • src/conf.c+45 9 modified
    @@ -23,6 +23,28 @@
     #include "xpath.h"
     #include "log.h"
     
    +static int pusb_conf_xpath_id_is_safe(const char *name, const char *value)
    +{
    +	const unsigned char *cursor;
    +
    +	if (value == NULL || value[0] == '\0')
    +	{
    +		log_error("%s is empty.\n", name);
    +		return 0;
    +	}
    +
    +	for (cursor = (const unsigned char *)value; *cursor != '\0'; ++cursor)
    +	{
    +		if (*cursor == '\'' || *cursor < 0x20)
    +		{
    +			log_error("%s contains an unsafe character for XPath lookup.\n", name);
    +			return 0;
    +		}
    +	}
    +
    +	return 1;
    +}
    +
     static void pusb_conf_options_get_from(
     	t_pusb_options *opts,
     	const char *from,
    @@ -55,16 +77,10 @@ static int pusb_conf_parse_options(
     	size_t xpath_size;
     	int i;
     
    -	// these can come from argv, so make sure nothing messes up snprintf later
    -	char xpath_user[32] = { };
    -	char xpath_service[32] = { };
    -	snprintf(xpath_user, sizeof(xpath_user), "%s", user);
    -	snprintf(xpath_service, sizeof(xpath_service), "%s", service);
    -
     	struct s_opt_list opt_list[] = {
     		{ CONF_DEVICE_XPATH, opts->device.name },
    -		{ CONF_USER_XPATH, xpath_user },
    -		{ CONF_SERVICE_XPATH, xpath_service },
    +		{ CONF_USER_XPATH, user },
    +		{ CONF_SERVICE_XPATH, service },
     		{ NULL, NULL }
     	};
     
    @@ -118,6 +134,11 @@ static int pusb_conf_parse_device(
     	char *deviceId
     )
     {
    +	if (!pusb_conf_xpath_id_is_safe("Device id", deviceId))
    +	{
    +		return 0;
    +	}
    +
     	pusb_conf_device_get_property(opts, doc, "vendor", opts->device_list[deviceIndex].vendor, sizeof(opts->device_list[deviceIndex].vendor), deviceId);
     	pusb_conf_device_get_property(opts, doc, "model", opts->device_list[deviceIndex].model, sizeof(opts->device_list[deviceIndex].model), deviceId);
     
    @@ -172,6 +193,11 @@ int pusb_conf_parse(
     	char device_xpath[sizeof(CONF_USER_XPATH) + CONF_USER_MAXLEN + sizeof("device")];
     
     	log_debug("Parsing settings...\n", user, service);
    +	if (!pusb_conf_xpath_id_is_safe("Username", user) ||
    +	    !pusb_conf_xpath_id_is_safe("Service", service))
    +	{
    +		return 0;
    +	}
     	if (strlen(user) > CONF_USER_MAXLEN)
     	{
     		log_error("Username \"%s\" is too long (max: %d).\n", user, CONF_USER_MAXLEN);
    @@ -220,7 +246,17 @@ int pusb_conf_parse(
     		}
     
     		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]);
    +		if (!pusb_conf_parse_device(opts, doc, currentDevice, device_list[currentDevice]))
    +		{
    +			xmlFreeDoc(doc);
    +			xmlCleanupParser();
    +
    +			for (int i = 0; i < 10; i++)
    +			{
    +				xfree(device_list[i]);
    +			}
    +			return 0;
    +		}
     	}
     
     	/* Mark devices that carry superuser="true" in the user's <device> elements. */
    
  • src/conf.h+2 2 modified
    @@ -65,8 +65,8 @@ typedef struct		pusb_options
     
     struct		s_opt_list
     {
    -	char	*name;
    -	char	*value;
    +	const char	*name;
    +	const char	*value;
     };
     
     int pusb_conf_init(t_pusb_options *opts);
    
  • tests/unit/c/conf_test.c+50 0 modified
    @@ -154,6 +154,53 @@ static void test_parse_username_too_long(void **state)
     		"abcdefghijklmnopqrstuvwxyz1234567", "login"));
     }
     
    +static void test_parse_rejects_xpath_user_injection(void **state)
    +{
    +	(void)state;
    +	t_pusb_options opts;
    +	pusb_conf_init(&opts);
    +	assert_int_equal(0, pusb_conf_parse(FIXTURE_CONF, &opts,
    +		"' or @id='user_mixed", "login"));
    +}
    +
    +static void test_parse_rejects_xpath_service_injection(void **state)
    +{
    +	(void)state;
    +	t_pusb_options opts;
    +	pusb_conf_init(&opts);
    +	assert_int_equal(0, pusb_conf_parse(FIXTURE_CONF, &opts,
    +		"user_mixed", "' or @id='sudo"));
    +}
    +
    +static void test_parse_rejects_xpath_device_id_injection(void **state)
    +{
    +	(void)state;
    +	char tmpfile[] = "/tmp/pamusb_test_device_xpath_XXXXXX";
    +	int fd = mkstemp(tmpfile);
    +	assert_true(fd >= 0);
    +	const char *xml =
    +		"<?xml version=\"1.0\"?>"
    +		"<configuration>"
    +		"  <devices>"
    +		"    <device id=\"victim\"><vendor>V</vendor><model>M</model>"
    +		"      <serial>S001</serial><volume_uuid>UUID-A</volume_uuid></device>"
    +		"  </devices>"
    +		"  <users>"
    +		"    <user id=\"testuser\"><device>' or @id='victim</device></user>"
    +		"  </users>"
    +		"  <services>"
    +		"    <service id=\"login\"></service>"
    +		"  </services>"
    +		"</configuration>";
    +	write(fd, xml, strlen(xml));
    +	close(fd);
    +
    +	t_pusb_options opts;
    +	pusb_conf_init(&opts);
    +	assert_int_equal(0, pusb_conf_parse(tmpfile, &opts, "testuser", "login"));
    +	unlink(tmpfile);
    +}
    +
     static void test_parse_user_not_in_conf(void **state)
     {
     	(void)state;
    @@ -219,6 +266,9 @@ int main(void)
     		cmocka_unit_test(test_parse_nonexistent_file),
     		cmocka_unit_test(test_parse_invalid_xml),
     		cmocka_unit_test(test_parse_username_too_long),
    +		cmocka_unit_test(test_parse_rejects_xpath_user_injection),
    +		cmocka_unit_test(test_parse_rejects_xpath_service_injection),
    +		cmocka_unit_test(test_parse_rejects_xpath_device_id_injection),
     		cmocka_unit_test(test_parse_user_not_in_conf),
     		cmocka_unit_test(test_superuser_attribute_case_sensitive),
     	};
    

Vulnerability mechanics

Root cause

"Missing input validation on user-supplied and device-supplied identifiers allows injection of XPath metacharacters into configuration queries."

Attack vector

An attacker can inject arbitrary XPath predicates by controlling any identifier used in the configuration queries. For PAM-supplied identifiers, an attacker can provide a crafted username (e.g., via SSH login attempt) containing a single-quote to break out of the XPath string literal and inject predicates like `' or @id='user_mixed` [ref_id=2][ref_id=1]. For device-supplied identifiers, an attacker can present a USB device with a crafted serial number, model, or vendor string containing XPath metacharacters. The injected predicates can alter which device entry is matched, potentially authenticating with a different user's device or bypassing device checks entirely [ref_id=2].

Affected code

The vulnerability resides in `src/conf.c` where XPath expressions are built from user-supplied identifiers (PAM username, service name) and device-supplied identifiers (USB device serial, model, vendor) to query `/etc/pamusb.conf`. The functions `pusb_conf_parse` and `pusb_conf_parse_device` directly used these identifiers in XPath queries without sanitization [patch_id=2749078]. The patch introduces a new validator `pusb_conf_xpath_id_is_safe()` that rejects identifiers containing single-quotes or control characters before they reach the XPath builder [ref_id=1].

What the fix does

The fix adds `pusb_conf_xpath_id_is_safe()` which validates that identifiers are non-empty and contain no single-quote (`'`) or control characters (0x00–0x1F) before they are used in XPath expressions [patch_id=2749078][ref_id=1]. This validator is called for usernames and service names in `pusb_conf_parse()`, and for device IDs in `pusb_conf_parse_device()`. The patch also removes the 32-byte temporary buffers (`xpath_user`, `xpath_service`) that could silently truncate lookup values, and adds proper error handling when device parsing fails [ref_id=3]. Unit tests confirm that injection attempts via username, service name, and device ID are now rejected [patch_id=2749078].

Preconditions

  • inputAttacker must be able to supply a PAM username (e.g., via SSH login attempt) or present a USB device with a crafted serial/model/vendor string
  • configThe target system must be running pam_usb prior to version 0.9.0

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.