Ledger Nano S+ (2c97:5000) rejected by USBGuard rule 39 on hotplug — reboot makes it work

Hi,

I’m running Debian 13 (Trixie) with Kicksecure hardening on bare metal, and I’m troubleshooting a Ledger Nano S+ USB authorization problem.

The Ledger itself is working correctly. I have tested two different Nano S+ devices, two original Ledger USB cables, and three different USB ports.

The interesting part is that the kernel detects the Ledger correctly, but USBGuard rejects it.

USBGuard configuration

USBGuard is active:

usbguard.service - USBGuard daemon
Active: active (running)

The kernel USB authorization default is:

$ cat /sys/module/usbcore/parameters/authorized_default
1

USBGuard is configured with:

$ sudo usbguard get-parameter ImplicitPolicyTarget
block

So the default policy is to block unknown USB devices.

Ledger detection

When I plug in the Ledger, dmesg shows:

usb 1-2: new full-speed USB device number 5 using xhci_hcd
usb 1-2: New USB device found, idVendor=2c97, idProduct=5000, bcdDevice= 2.01
usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-2: Product: Nano S+
usb 1-2: Manufacturer: Ledger
usb 1-2: SerialNumber: 0001
usb 1-2: Device is not authorized for usage
usb 1-2: USB disconnect, device number 5

The same happens on a second connection attempt.

USBGuard watch shows the reason

Running:

sudo usbguard watch

produces:

[device] PresenceChanged: id=63
 event=Insert
 target=block
 device_rule=block id 2c97:5000 serial "0001" name "Nano S+"
 ...
 with-interface { 03:00:00 ff:ff:ff 03:01:01 }

[device] PolicyChanged: id=63
 target_old=block
 target_new=reject
 ...
 rule_id=39

[device] PolicyApplied: id=63
 target_new=reject
 ...
 rule_id=39

[device] PresenceChanged: id=63
 event=Remove
 target=reject

I then checked the actual USBGuard policy:

$ sudo usbguard list-rules | nl -ba | sed -n '35,42p'

35  35: reject with-interface all-of { 03:*:* dc:*:* }
36  36: reject with-interface all-of { 03:*:* e0:*:* }
37  37: reject with-interface all-of { 03:*:* ef:*:* }
38  38: reject with-interface all-of { 03:*:* fe:*:* }
39  39: reject with-interface all-of { 03:*:* ff:*:* }
40  40: reject with-interface one-of { ef:04:* }
41  41: allow with-interface 03:01:01 if !allowed-matches(with-interface equals { 03:01:01 })
42  42: allow with-interface 03:01:02 if !allowed-matches(with-interface equals { 03:01:02 })

The Ledger reports these interfaces:

{ 03:00:00 ff:ff:ff 03:01:01 }

Therefore rule 39 appears to match the Ledger because of:

ff:ff:ff

and USBGuard changes the device from block to reject.

The kernel then reports:

Device is not authorized for usage

and disconnects it.

However, rebooting changes the situation

I discovered an interesting workaround.

If I connect the Ledger, enter the PIN, and then reboot the system, after reboot the Nano can become available.

In the working state:

$ lsusb | grep -i -E 'ledger|2c97'
Bus 001 Device 002: ID 2c97:5000 Ledger Nano S+

and USBGuard shows:

55: allow id 2c97:5000 serial "0001" name "Nano S+"
    ...
    via-port "1-2"
    with-interface { 03:00:00 ff:ff:ff 03:01:01 }

Ledger Live then recognizes the device and I have verified the backup.

Interestingly, ImplicitPolicyTarget is still:

block

so I am not using ImplicitPolicyTarget=allow as a permanent workaround.

I also tested temporarily setting:

sudo usbguard set-parameter ImplicitPolicyTarget allow

but I don’t want to weaken the USB security policy just to make the Ledger work.

Question

Could someone explain the intended USBGuard/Kicksecure configuration for a Ledger Nano S+ in this situation?

Specifically:

  1. Is rule 39 (reject with-interface ... ff:*:*) expected to reject the Ledger’s ff:ff:ff interface?
  2. Should there be a Ledger-specific exception that takes precedence over this rule?
  3. Why does a reboot allow the Ledger to become usable despite the same generic reject rule being present?
  4. What is the recommended way under Kicksecure to permanently allow this specific Ledger while keeping:
ImplicitPolicyTarget=block

?

I would prefer not to remove rule 39 globally, since it appears to be a security rule for a reason.

Thanks!

Note: This investigation and forum post were prepared with assistance from ChatGPT.

Update: I have now tested both of my Ledger Nano S+ devices. Both exhibit exactly the same behavior. Hot-plugging either device results in the same USBGuard rule_id=39 rejection (ff:ff:ff interface), followed by kernel Device is not authorized for usage.

The same devices become usable after the previously described reboot procedure (Ledger connected, PIN entered, then system reboot). Ledger Live subsequently detects and verifies both devices.

This therefore does not appear to be specific to one Ledger device. The behavior is reproducible with two independent Nano S+ devices.

See:

Yes.

Probably not. Every additional device we allow adds more attack surface to the system.

Because devices present during bootup are always allowed. This is so we don’t break internal hardware devices on systems that happen to use the USB bus for those devices. (Things like touchscreens, Bluetooth, webcams, etc. often use USB even though they’re internal devices.)

The recommended workflow is to not permanently allow it at all. You should get a desktop notification when you plug the device in that a) tells you the device is blocked, and b) provides you a button you can click to temporarily unblock it. Plug in your device, then click the “Allow” button in the popup, and you should be set.

If you really want to allow it permanently, you probably will want to add a new rule to USBGuard’s configuration. A good resource for that is here:

And see also:

2 Likes

I had a similar issue with my wallets, I had to add them to the rules to allow permanently. I just run usbguard watch, copy all the description of the device and replace the block/request with allow.

These commands allow you that
sudo usbguard --help
sudo usbguard allow-device --permanent <rule>
sudo usbguard append-rule <rule>  
sudoedit /etc/usbguard/rules.conf

In my case the notification buttons don’t work because before be able to allow it, the wallet has already been blocked and removed from the system so it isn’t detecting it yet.
Test it if you can but it doesn’t happen with other blocked devices I’ve.

With the wallets the flow is:

  1. event=Insert
  2. target=block
  3. target_new=reject
  4. event=Remove

But with other devices the flow is:

  1. event=Insert
  2. target=block
  3. HEAR I SEE THE NOTIFICATION AND PRESS ALLOW
  4. target_old=block
    target_new=allow

The device is not rejected automatically like in the log of @kick , which is the same I’ve with other wallets I’ve tested

1 Like

I tested both of my Ledger Nano S+ devices, and both behave identically.

When the Ledger is connected before boot, it works normally:

lsusb | grep -i -E 'ledger|2c97'
Bus 001 Device 002: ID 2c97:5000 Ledger Nano S+

However, when the device is unplugged and reconnected after the system has booted, it is immediately rejected.

usbguard watch shows:

PresenceChanged: id=63
event=Insert
target=block

followed by:

PolicyChanged: id=63
target_old=block
target_new=reject
rule_id=39

and:

PolicyApplied: id=63
target_new=reject

followed by the device removal event.

The kernel confirms the same:

usb 1-2: New USB device found, idVendor=2c97, idProduct=5000
usb 1-2: Product: Nano S+
usb 1-2: Manufacturer: Ledger
usb 1-2: SerialNumber: 0001
usb 1-2: Device is not authorized for usage
usb 1-2: USB disconnect

KDE Plasma shows no USB authorization notification, and the device does not remain visible in KDE Info.

Rule 39 is:

39: reject with-interface all-of { 03:*:* ff:*:* }

The Ledger reports:

{ 03:00:00 ff:ff:ff 03:01:01 }

Therefore, rule 39 appears to match the Ledger’s ff:*:* interface and reject the device during hotplug.

Conclusion: Both Nano S+ devices reproduce exactly the same behavior. This appears to be a USBGuard policy/interface-matching issue rather than a hardware, cable, or Ledger-device problem.


It forms part of the Kicksecure configuration for USBGuard

#
# Present device policy.
#
# How to treat devices that are already connected when the
# daemon starts. One of:
#
# * allow        - authorize every present device
# * block        - deauthorize every present device
# * reject       - remove every present device
# * keep         - just sync the internal state and leave it
# * apply-policy - evaluate the ruleset for every present
#                  device
#
# Overridden by Kicksecure to allow all devices that are connected at startup.
#
PresentDevicePolicy=allow

That is indeed weird. Maybe the wallet figures out that it’s not been allowed and automatically disconnects itself. In that case, whitelisting it is probably the best you can do.

2 Likes