# General note about security

`pam_usb` is intended as an "user comfort" utility. While it can enhance security, if used as a second factor, it can also reduce it.

Make sure you are aware of how it works and what you combine it with (see other warnings).

Also I want to point it that this is barely audited. I've tried to raise funds for it but there was literally no interest in it seemingly...

# Known CVEs and security advisories

pam_usb has gone through an ongoing security audit. The advisories below have been
**published** and are **fixed** in the listed release — update to at least that
version (ideally the latest) to be protected. Full write-ups (impact, affected
versions, patches, credits) are available on the
[GitHub Security Advisories page](https://github.com/mcdope/pam_usb/security/advisories).

CVSS scores are v3.1 base scores as recorded on the advisory. Advisories scoring
below CVSS 4.0 are not assigned a CVE (shown as `—` in the CVE column) and are
referenced by their GHSA identifier.

> **Note:** Additional advisories from later audit rounds are being prepared and
> will be published together with the release that fixes them. Per our
> [responsible disclosure policy](https://github.com/mcdope/pam_usb/blob/master/SECURITY.md),
> unfixed issues are not detailed publicly until a fix ships.

### Fixed in 0.8.7

| CVE | GHSA | Severity | CVSS | Summary |
|-----|------|----------|:----:|---------|
| [CVE-2026-44713](https://github.com/mcdope/pam_usb/security/advisories/GHSA-822m-whrh-vrj8) | GHSA-822m-whrh-vrj8 | High | 8.8 | Command injection via `$TMUX` environment variable leads to RCE as root |
| [CVE-2026-44712](https://github.com/mcdope/pam_usb/security/advisories/GHSA-jgv5-w6rm-7wxg) | GHSA-jgv5-w6rm-7wxg | High | 8.2 | Shell injection via device UUID and username in `pamusb-conf` / `pamusb-agent` |
| [CVE-2026-44711](https://github.com/mcdope/pam_usb/security/advisories/GHSA-fjpm-p9pj-mp34) | GHSA-fjpm-p9pj-mp34 | High | 7.9 | Symlink attacks on the pad directory/files enable auth bypass and root file corruption |
| [CVE-2026-44709](https://github.com/mcdope/pam_usb/security/advisories/GHSA-jxrj-q67x-wr4c) | GHSA-jxrj-q67x-wr4c | High | 7.8 | `PINENTRY_FALLBACK_APP` allows arbitrary command execution; keyring password exposed in process args |
| [CVE-2026-44710](https://github.com/mcdope/pam_usb/security/advisories/GHSA-j8cq-2gv6-gfwf) | GHSA-j8cq-2gv6-gfwf | Medium | 4.6 | NULL pointer dereference from UDisks device fields causes PAM crash (login DoS) |

### Fixed in 0.9.0

| CVE | GHSA | Severity | CVSS | Summary |
|-----|------|----------|:----:|---------|
| [CVE-2026-47269](https://github.com/mcdope/pam_usb/security/advisories/GHSA-jmmj-qhrq-w45g) | GHSA-jmmj-qhrq-w45g | High | 7.4 | `deny_remote` incorrectly classifies IPv4-mapped IPv6 remote connections as local |
| — | GHSA-7cgr-4c38-59h2 | High | 7.4 | Local authentication check bypass via incorrect process ancestry and session identity parsing |
| [CVE-2026-47272](https://github.com/mcdope/pam_usb/security/advisories/GHSA-vx6f-rrqr-j87c) | GHSA-vx6f-rrqr-j87c | High | 7.1 | OTP pad authentication bypass via missing system pad check and uninitialized RNG buffer |
| [CVE-2026-47273](https://github.com/mcdope/pam_usb/security/advisories/GHSA-vfj3-5h5v-6g93) | GHSA-vfj3-5h5v-6g93 | Medium | 6.5 | XPath injection via PAM-supplied identifiers in configuration queries |
| [CVE-2026-47274](https://github.com/mcdope/pam_usb/security/advisories/GHSA-pp29-w28g-r9h9) | GHSA-pp29-w28g-r9h9 | Medium | 6.3 | Uncontrolled search path in the tools allows privilege escalation via `PATH` manipulation |
| [CVE-2026-47270](https://github.com/mcdope/pam_usb/security/advisories/GHSA-j3xw-vc43-x7jg) | GHSA-j3xw-vc43-x7jg | Medium | 6.3 | `strtok()` race condition in multi-threaded PAM hosts can corrupt the `deny_remote` result |
| [CVE-2026-47271](https://github.com/mcdope/pam_usb/security/advisories/GHSA-7rvx-jcc6-7hqq) | GHSA-7rvx-jcc6-7hqq | Medium | 5.1 | OOM guards removed by `-DNDEBUG` cause NULL dereference and auth process crash |

### Fixed in 0.9.1

| CVE | GHSA | Severity | CVSS | Summary |
|-----|------|----------|:----:|---------|
| [CVE-2026-48064](https://github.com/mcdope/pam_usb/security/advisories/GHSA-w38v-cw9r-x9p6) | GHSA-w38v-cw9r-x9p6 | High | 8.1 | `PAM_RHOST` check skipped when `deny_remote=false` allows XDMCP authentication bypass |
| [CVE-2026-48065](https://github.com/mcdope/pam_usb/security/advisories/GHSA-24mw-m2vf-36vp) | GHSA-24mw-m2vf-36vp | Medium | 6.7 | Unchecked integer multiplication before `xmalloc()` allows heap overflow on 32-bit targets |
| [CVE-2026-48066](https://github.com/mcdope/pam_usb/security/advisories/GHSA-qg76-57wq-mpv6) | GHSA-qg76-57wq-mpv6 | Medium | 5.7 | Thread-unsafe static pointer in `log.c` causes a data race under concurrent PAM auth |
| [CVE-2026-48792](https://github.com/mcdope/pam_usb/security/advisories/GHSA-pvrg-chgw-x42c) | GHSA-pvrg-chgw-x42c | Medium | 4.4 | `pusb_has_virtual_input_device()` silently discards `EACCES`, disabling remote-desktop detection under non-root execution |

### Fixed in 0.9.2

| CVE | GHSA | Severity | CVSS | Summary |
|-----|------|----------|:----:|---------|
| [CVE-2026-48981](https://github.com/mcdope/pam_usb/security/advisories/GHSA-96vv-r4wc-28c2) | GHSA-96vv-r4wc-28c2 | Medium | 6.7 | `xmlReadFile` with default flags permits XXE network entity fetching in `conf.c` |
| [CVE-2026-48980](https://github.com/mcdope/pam_usb/security/advisories/GHSA-qr83-mf3h-fvqr) | GHSA-qr83-mf3h-fvqr | Medium | 6.3 | `getenv()` in PAM context allows environment variable injection into local-check logic |
| [CVE-2026-48982](https://github.com/mcdope/pam_usb/security/advisories/GHSA-hxh6-9574-5vp6) | GHSA-hxh6-9574-5vp6 | Medium | 5.8 | Missing `O_EXCL` on pad temp-file creation allows a concurrent update race |
| [CVE-2026-48983](https://github.com/mcdope/pam_usb/security/advisories/GHSA-4j8q-67fq-3xc3) | GHSA-4j8q-67fq-3xc3 | Medium | 5.8 | TOCTOU race in pad directory creation allows symlink substitution |
| [CVE-2026-48985](https://github.com/mcdope/pam_usb/security/advisories/GHSA-7j6h-wfc2-mg5q) | GHSA-7j6h-wfc2-mg5q | Medium | 5.5 | NULL dereference crash in `pusb_is_loginctl_local` when `loginctl` returns an empty Remote field |
| [CVE-2026-48986](https://github.com/mcdope/pam_usb/security/advisories/GHSA-h28h-9hc3-v595) | GHSA-h28h-9hc3-v595 | Medium | 4.7 | Infinite-loop DoS in the process-tree walk when the parent process exits during authentication |
| [CVE-2026-48984](https://github.com/mcdope/pam_usb/security/advisories/GHSA-rmp6-wfrq-wrrc) | GHSA-rmp6-wfrq-wrrc | Medium | 4.7 | `xfree()` does not call `explicit_bzero` — sensitive cryptographic material may linger in freed heap |
| — | GHSA-64g7-3jrm-4jc3 | Low | 3.6 | Missing `O_NOFOLLOW` in `evdev.c` allows symlink following when opening device nodes |
| — | GHSA-vw5x-vxgv-fgxm | Low | 3.3 | `BUFSIZ` truncation in `pusb_get_process_envvar` may cause false-negative TMUX detection |
| — | GHSA-xgfj-3wm2-gvx8 | Low | 2.5 | `fopen` without `O_CLOEXEC` in `process.c` leaks `/proc` file descriptors into `popen` children |
| — | GHSA-rfcg-6wgv-77hw | Low | 1.9 | Integer overflow UB in `pusb_xpath_get_time()` via `atoi` and signed multiplication |
| — | GHSA-chgp-j28w-mx9q | Low | 1.8 | Per-device config `<option>` elements were silently never applied during config parsing |

### Fixed in 0.9.3

| CVE | GHSA | Severity | CVSS | Summary |
|-----|------|----------|:----:|---------|
| — | GHSA-gc4c-v52c-2v34 | High | 7.4 | `deny_remote` bypassed by compressed IPv6 addresses (e.g. `::1`, `fe80::1`) in `pusb_tmux_has_remote_clients` |
| — | GHSA-7583-9cqv-j9rf | High | 7.4 | `deny_remote` bypassed by zone-indexed IPv6 addresses (e.g. `fe80::1%eth0`) in `pusb_tmux_has_remote_clients` |
| — | GHSA-ffx6-xjp6-x33g | High | 7.1 | `getenv()` in `pusb_local_login` allows the XRDP `deny_remote` block to be bypassed by unsetting `XRDP_SESSION` |
| — | GHSA-gc6f-5hpq-ghxh | Medium | 4.4 | Heap out-of-bounds read in `pusb_get_tty_from_display_server` when `/proc/<pid>/cmdline` fills the read buffer |

# Warning about XDMCP

This warning has been **partially mitigated** since 0.9.1 but XDMCP + pam_usb is still not recommended. Read carefully.

**What changed in 0.9.1 (GHSA-w38v-cw9r-x9p6):** The root cause of the original bypass was that the `PAM_RHOST` check was gated behind the `deny_remote` option. Display managers whitelisted with `deny_remote=false` (e.g. `gdm-password`, `lightdm`) also skipped the `PAM_RHOST` check entirely, so a remote XDMCP client was never identified as remote by pam_usb.

Since 0.9.1, the `PAM_RHOST` check runs unconditionally regardless of `deny_remote`. When a display manager correctly sets `PAM_RHOST` to the remote client's IP for XDMCP connections, pam_usb will detect it and return `PAM_IGNORE`.

**Why this is still not a guarantee:** `PAM_RHOST` is set by the display manager, not by the kernel or PAM itself. Whether a given DM sets it correctly for XDMCP depends on its implementation. Major display managers (GDM, LightDM) do set `PAM_RHOST` for remote sessions; less common or older DMs may not. If your DM does not set `PAM_RHOST` for XDMCP connections, the 0.9.1 fix offers no protection.

The `remote_desktop_check` option (enabled by default since 0.9.0) provides an additional layer via evdev virtual device detection, which XDMCP sessions typically trigger. However this too is not a guaranteed catch-all.

**Recommendation:** XDMCP is an unencrypted, largely deprecated protocol that should be avoided on general security grounds regardless of pam_usb. If you must use it, ensure you are on 0.9.1+ and verify that your specific display manager sets `PAM_RHOST` for XDMCP connections before relying on pam_usb as a meaningful security control.

# Warning about remote desktop tools

Since v0.9.0, pam_usb ships built-in remote desktop detection controlled by the `remote_desktop_check` option (default: `true`). When enabled, authentication is denied if a known remote desktop service is detected as active. The check only runs when `deny_remote` is also enabled.

## What is detected

| Tool | Detection method |
|------|-----------------|
| **AnyDesk** | Process presence alone — denied even if no remote session is active |
| **VNC** (x11vnc, Xvnc, GNOME Remote Desktop/VNC) | Process running **and** external established connection on port 5900 |
| **RDP** (GNOME Remote Desktop/RDP) | Process running **and** external established connection on port 3389 |
| **TeamViewer** | Process running **and** outbound connection to TeamViewer relay servers (port 5938) |
| **Virtual input devices** | Any tool that injects synthetic input via a `BUS_VIRTUAL` evdev device with no physical path |

All checks exclude loopback connections — a VNC or RDP session over localhost is not treated as remote.

## What is still NOT reliably detected

- TeamViewer in idle state (daemon running but no active remote session)
- x11vnc / Xvnc with no client currently connected
- VNC or RDP servers listening on non-standard ports
- GNOME desktop sharing over WebRTC (newer GNOME Remote Desktop protocol)
- Parsec, Rustdesk, Chrome Remote Desktop, NoMachine, and other tools not listed above

There will likely always be some remote access software that can circumvent the check. If this is a concern, use pam_usb as a second factor (`required` instead of `sufficient` in your PAM stack) so a password is still required even when pam_usb grants access.

## Known false positives

The virtual input device check (`BUS_VIRTUAL` evdev devices) can produce false positives for locally-running tools that happen to create synthetic input devices:

- Screen readers and on-screen keyboards
- Drawing tablet drivers (Wacom, Huion)
- Key remappers (keyd, xremap, input-remapper)
- Accessibility software

If pam_usb unexpectedly denies access and you do not use any remote desktop tools, set `remote_desktop_check=false` in the relevant section of `/etc/security/pam_usb.conf` to disable the check.

# Warning about remote access (SSH etc)

In the past there have been ways to circumvent the local check (see issue [#51](https://github.com/mcdope/pam_usb/issues/51) and also the "[cup of tee](https://github.com/mcdope/pam_usb/issues/39)"). I'm confident that all known ways are fixed now.

But I need to underline "known"... I'm no security expert and it's very well possible that there are still ways to circumvent the checks.

Kudos to [@Fuseteam](https://github.com/Fuseteam) for extensive testing, breaking and reporting.

# Warning about the ability to unlock the GNOME keyring

If you use the ability to auto-unlock your keyring while using pam_usb you are exposing your keyring password to everyone being able to access the file containing it.

This means at least root (and users able to use sudo) can read your keyring password in cleartext. If you use the SAMBA feature to share your home directory you will also expose it to everyone allowed to access that share.

For most users this isn't an issue. But if you're sharing your machine with other users, or use it in a network, you should for sure keep it in mind.

# Require a specific device for superuser access

In case you want to use multiple devices, and enable only one (or some) of them for superuser grants (su, sudo, polkit) you can do so with some manual configuration. You need to be sure about covering every mechanism/pam service used in your system though, in example if you enable it for sudo only but the user is able to run pkexec it will obviously fail to take effect.

Available from v0.8.7.

This feature allows you to designate specific USB devices as **superuser-capable** — valid for privilege-escalation services such as `sudo`, `su`, and `polkit-1` — while other registered devices remain valid only for non-privileged services (login, screensaver unlock, etc.).

A single `superuser="true"` attribute on a `<device>` element covers all services you mark as requiring it. No per-service device entries are needed.

**Use case:** In a shared-account environment (e.g. school lab), students and teachers use the same Unix account. Students carry a StudentUSB that lets them log in; teachers carry a TeacherUSB that additionally grants `sudo` access. The configuration enforces this without creating separate accounts.

---

### How it works

Two things must be configured for the restriction to take effect:

1. **Mark the device** as superuser-capable in the `<user>` block, using the `superuser="true"` attribute on the `<device>` element.
2. **Mark the service** as requiring a superuser device, by setting `<option name="superuser">true</option>` inside the corresponding `<service>` block.

When a user authenticates against a service marked with `superuser=true`, any device in their list that does **not** carry the `superuser="true"` attribute is silently excluded for that authentication attempt. If no superuser-capable device is present, authentication is denied.

Services **not** marked with `superuser=true` are unaffected — all registered devices for the user remain valid (fully backward-compatible).

---

### Option reference

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `superuser` | Boolean | `false` | When set on a `<service>`, only devices with `superuser="true"` in the user's device list are accepted for that service. No effect when set on `<defaults>`, `<devices>`, or `<users>`. |

---

### User configuration

The `<device>` element under `<user>` accepts an optional `superuser` XML attribute:

| Name | Type | Description | Example |
|------|------|-------------|---------|
| `superuser` | Attribute (optional) | Marks this device as valid for superuser services. Devices without this attribute are excluded when the service requires superuser. | `superuser="true"` |

```xml
<user id="alex">
    <!-- Works for login and all non-superuser services -->
    <device>StudentUSB</device>

    <!-- Works for all services including sudo, su, polkit-1 -->
    <device superuser="true">TeacherUSB</device>
</user>
```

A device with `superuser="true"` is **not** restricted to superuser services — it works for every service, including login. The attribute only adds eligibility for superuser-marked services.

---

### Service configuration

Mark any service as requiring a superuser-capable device by adding the `superuser` option:

```xml
<service id="sudo">
    <option name="superuser">true</option>
</service>

<service id="su">
    <option name="superuser">true</option>
</service>

<!-- polkit-1 is also whitelisted for deny_remote by default -->
<service id="polkit-1">
    <option name="superuser">true</option>
    <option name="deny_remote">false</option>
</service>
```

You can apply the `superuser` option to any service name that appears in your PAM configuration — it is not limited to `sudo`, `su`, and `polkit-1`.

---

### Complete example

**Scenario:** A shared account `labuser` has two USB keys. The StudentUSB grants login only; the TeacherUSB grants login and sudo/pkexec.

```xml
<configuration>
    <devices>
        <device id="StudentUSB">
            <vendor>SanDisk Corp.</vendor>
            <model>Cruzer Blade</model>
            <serial>SNDKXXXXXXXXXXXXXXXX</serial>
            <volume_uuid>AAAA-1111</volume_uuid>
        </device>

        <device id="TeacherUSB">
            <vendor>Kingston</vendor>
            <model>DataTraveler</model>
            <serial>KNGXXXXXXXXXXXXXXXXX</serial>
            <volume_uuid>BBBB-2222</volume_uuid>
        </device>
    </devices>

    <users>
        <user id="labuser">
            <device>StudentUSB</device>
            <device superuser="true">TeacherUSB</device>
        </user>
    </users>

    <services>
        <service id="sudo">
            <option name="superuser">true</option>
        </service>
        <service id="su">
            <option name="superuser">true</option>
        </service>
        <service id="polkit-1">
            <option name="superuser">true</option>
            <option name="deny_remote">false</option>
        </service>
    </services>
</configuration>
```

**Result:**

| Device | Service | Outcome |
|--------|---------|---------|
| StudentUSB | login, gdm, screensaver | Allowed |
| StudentUSB | sudo, su, polkit-1 | **Denied** - no `superuser` attribute |
| TeacherUSB | login, gdm, screensaver | Allowed |
| TeacherUSB | sudo, su, polkit-1 | Allowed |

---

### Log output

If all of a user's devices are excluded by the superuser filter, pam_usb emits an error visible in the system log even without debug mode *(from v0.9.2)*:

```
Service "sudo" for user "alex" requires a superuser-capable device but none of the registered devices have the superuser attribute.
```

With `<option name="debug">true</option>`, the per-device filtering decisions are also logged:

```
Service "sudo" requires superuser device. Filtering device list.
Device "StudentUSB" excluded for service "sudo" (no superuser attribute).
```

---

### Notes

- The restriction is configured entirely in `/etc/security/pam_usb.conf` — no PAM stack changes are needed.
- Adding more superuser-granted services requires only a new `<service>` entry; no change to the device or user configuration is needed.
- If a user has no device with `superuser="true"` and authenticates against a superuser-marked service, pam_usb denies the request. The fallback PAM module (e.g. `pam_unix`) still runs if configured as `sufficient` rather than `required`.
- Use `pamusb-conf --add-user=<username> --superuser` to set `superuser="true"` on the `<device>` element automatically (available from v0.9.0). The `--superuser` flag can only be combined with `--add-user`. Alternatively, add the attribute manually to the `<device>` element in the config file.