Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB resets leave the reader connection in a broken state #154

Closed
9ary opened this issue Apr 11, 2023 · 29 comments
Closed

USB resets leave the reader connection in a broken state #154

9ary opened this issue Apr 11, 2023 · 29 comments
Assignees

Comments

@9ary
Copy link

9ary commented Apr 11, 2023

Versions

  • smart card reader driver name and version: unsure
  • pcsc-lite version: 1.9.9
  • the output of the command /usr/sbin/pcscd --version
$ pcscd --version
pcsc-lite version 1.9.9.
Copyright (C) 1999-2002 by David Corcoran <[email protected]>.
Copyright (C) 2001-2022 by Ludovic Rousseau <[email protected]>.
Copyright (C) 2003-2004 by Damien Sauveron <[email protected]>.
Report bugs to <[email protected]>.
Enabled features: Linux x86_64-pc-linux-gnu libsystemd serial usb libudev usbdropdir=/usr/lib/pcsc/drivers ipcdir=/run/pcscd filter configdir=/etc/reader.conf.d
MAX_READERNAME: 128, PCSCLITE_MAX_READERS_CONTEXTS: 16

Platform

  • Operating system or GNU/Linux distribution name and version: Arch Linux, up to date
  • Smart card middleware name and version: unsure
  • Smart card reader manufacturer name and reader model name: Yubico Yubikey 5 NFC
  • Smart card name: same as above (USB card)

Issue

Originally reported at FiloSottile/yubikey-agent#142.

I initially ran into this because I've disabled always-on USB on my laptop.

Steps to reproduce:

  • establish a yubikey connection (by using yubikey-agent normally)
  • put computer to sleep
  • unplug key
  • plug it back into the same port (it can be a different yubikey, I do not have access to different reader models to test but I suspect a different VID/PID would avoid this problem)
  • wake computer
  • try to use agent again

At this point, yubikey-agent fails to reconnect to the key. I see log messages such as could not reach YubiKey: selecting piv applet: command failed: transmitting request: an attempt was made to end a non-existent transaction and could not reach YubiKey: connecting to smart card: the smart card cannot be accessed because of other connections outstanding.
Looking at https://github.com/go-piv/piv-go/blob/master/piv/pcsc_errors, these match SCARD_E_NOT_TRANSACTED and SCARD_E_SHARING_VIOLATION.

Also looks like the kernel only sees a device reset, rather than a new device. I see these messages in dmesg during wakeup:

[  +0.233951] usb 1-2: reset full-speed USB device number 62 using xhci_hcd

and

[  +0.351115] usb 1-2: usbfs: process 252659 (pcscd) did not claim interface 1 before use

I can fix it by unplugging the key and plugging it back in again, or restarting either yubikey-agent or pcscd.

Moving the key to a different USB port or waiting for the computer to be awake before reinserting it doesn't trigger the problem.

The monitor example from yubikey.rs does not see any events. I suppose pcsc_scan would yield the same result.

Log

Here's a log of me plugging the key in, using it successfully once, reproducing the broken state, trying to use the key again, then cycling it and using it successfully once again: https://gist.github.com/9ary/ec325e4370e0fd97cc7b831e9baa1fd4.

@LudovicRousseau
Copy link
Owner

Can you indicate in the log when the computer goes to sleep please.

I also see a "USB Device removed" event. Have you removed the token while the computer was awake?

@LudovicRousseau LudovicRousseau self-assigned this Apr 11, 2023
@9ary
Copy link
Author

9ary commented Apr 11, 2023

Can you indicate in the log when the computer goes to sleep please.

No problem, I'll have to capture another log.

I also see a "USB Device removed" event. Have you removed the token while the computer was awake?

Yes, as I said in the original post, I unplug and replug to key ("cycle") to show that this fixes it. I can leave that part out in the new log.

@9ary
Copy link
Author

9ary commented Apr 11, 2023

I've added a second, annotated log to the same gist link, scroll down to find it.

@LudovicRousseau
Copy link
Owner

The first command after the resume fails. The reader returns the error 0xFC "Overrun error"

17781409 winscard_svc.c:361:ContextThread() Received command: TRANSMIT from client 12
00000019 readerfactory.c:866:RFReaderInfoById() RefReader() count was: 1
00000003 winscard.c:1596:SCardTransmit() Send Protocol: T=1
00000003 APDU: 00 CB 3F FF 05 5C 03 5F FF 01 
00000003 ifdhandler.c:1398:IFDHTransmitToICC() usb:1050/0406:libudev:1:/dev/bus/usb/001/069 (lun: 0)
00000002 commands.c:1679:CmdXfrBlockAPDU_extended() T=0 (extended): 10 bytes
00000005 -> 000000 6F 0A 00 00 00 00 1C 00 00 00 00 CB 3F FF 05 5C 03 5F FF 01 
00000841 <- 000000 81 00 00 00 00 00 1C 40 FC 00 
00000004 commands.c:1572:CCID_Receive Overrun error
00000001 SW: 
00000002 ifdwrapper.c:543:IFDTransmit() Card not transacted: 612
00000002 winscard.c:1621:SCardTransmit() Card not transacted: rv=SCARD_E_NOT_TRANSACTED

My problem is that I do not see any notification that the USB device has been removed and inserted again.
I am not really surprised since the token has been removed while the computer was asleep. The Linux kernel is not able to detect that event.

To be sure you can run the command udevadm monitor and to the same manipulations:

  • computer sleep
  • unplug USB
  • replug USB
  • computer wake up

And compare with the output of udevadm monitor when you do:

  • computer sleep
  • computer wake up

Do you have differences?

Do you also have the problem when you do a sleep/wake up without removing the USB device?

@9ary
Copy link
Author

9ary commented Apr 11, 2023

My problem is that I do not see any notification that the USB device has been removed and inserted again. I am not really surprised since the token has been removed while the computer was asleep. The Linux kernel is not able to detect that event.

Indeed, however I do see the USB reset event in dmesg. I haven't tried, but I suspect that triggering a reset from software might have a similar effect. Maybe that would be helpful?

Do you have differences?

Yes, I see the following two lines in the log when I unplug the device:

KERNEL[108374.500888] unbind   /devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1 (usb)
UDEV  [108374.517895] unbind   /devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1 (usb)

These are the only events relating to the yubikey in the udev log. I've posted both logs to the gist in case you want to have a look.

Do you also have the problem when you do a sleep/wake up without removing the USB device?

No, in that case the device remains usable after resuming from sleep, and the ongoing transaction survives (unless I disable "always on USB" in the system firmware, which causes VBUS to be powered down during sleep, and thus has the same effect as unplugging the token).

@smlx
Copy link

smlx commented Apr 11, 2023

Hi, I think that this bug might be caused the order in which piv-go is closing the connection and transaction.

I've reproduced the issue locally, and confirmed that this patch fixes it for me. @9ary could you check if it works for you? If so I'll send a PR to piv-go.

I guess that the transaction should be closed before the connection. Is that correct @LudovicRousseau ?

@LudovicRousseau
Copy link
Owner

pcscd is looking for "add" & "remove" events, not "unbind".

It is strange that the kernel does not re-enumerate the USB devices after cutting the power of the USB devices.
I am not sure I can fix the problem in pcscd. It is more the job of the kernel.

@9ary
Copy link
Author

9ary commented Apr 11, 2023

Indeed, however I do see the USB reset event in dmesg. I haven't tried, but I suspect that triggering a reset from software might have a similar effect. Maybe that would be helpful?

Following this SE answer, I've issued a reset to the device.

dmesg:

[Apr11 19:22] usb 1-2: reset full-speed USB device number 71 using xhci_hcd
[  +0.542132] usb 1-2: usbfs: process 282173 (pcscd) did not claim interface 1 before use
$ udevadm monitor             
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[110468.335745] unbind   /devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1 (usb)
UDEV  [110468.342620] unbind   /devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1 (usb)

pcscd log:

06213326 ccid_usb.c:1532:InterruptRead() after (0) (5)
00000030 ccid_usb.c:1545:InterruptRead() InterruptRead (1/71): LIBUSB_TRANSFER_NO_DEVICE
00400280 ifdhandler.c:1955:IFDHICCPresence() usb:1050/0406:libudev:1:/dev/bus/usb/001/071 (lun: 0)
00000040 -> 000000 65 00 00 00 00 00 0B 00 00 00 
00000361 <- 000000 81 00 00 00 00 00 0B 00 00 00 
00000022 ifdhandler.c:2081:IFDHICCPresence() Card present
00000044 ifdhandler.c:318:IFDHPolling() usb:1050/0406:libudev:1:/dev/bus/usb/001/071 (lun: 0) 600000 ms
00000019 ccid_usb.c:1486:InterruptRead() before (0), timeout: 600000 ms
00008352 ccid_usb.c:1532:InterruptRead() after (0) (0)
00000048 NotifySlotChange: 50 03 
00000010 ifdhandler.c:1955:IFDHICCPresence() usb:1050/0406:libudev:1:/dev/bus/usb/001/071 (lun: 0)
00000010 -> 000000 65 00 00 00 00 00 0C 00 00 00 
00000397 <- 000000 81 00 00 00 00 00 0C 00 00 00 
00000022 ifdhandler.c:2081:IFDHICCPresence() Card present
00000010 ifdhandler.c:318:IFDHPolling() usb:1050/0406:libudev:1:/dev/bus/usb/001/071 (lun: 0) 600000 ms
00000007 ccid_usb.c:1486:InterruptRead() before (0), timeout: 600000 ms

So far, this is everything I can see after resuming when I've unplugged the device, however the transaction survives and I don't have to enter my PIN again (and it seems yubikey-agent is none the wiser).

Hi, I think that this bug might be caused the order in which piv-go is closing the connection and transaction.

I've reproduced the issue locally, and confirmed that this patch fixes it for me. @9ary could you check if it works for you? If so I'll send a PR to piv-go.

I guess that the transaction should be closed before the connection. Is that correct @LudovicRousseau ?

I'm not entirely sure this is correct. Your patch is closing the PCSC context then the card transaction. I think what it's really fixing is that maybe we shouldn't attempt to close the transaction at all when we receive an error? Maybe closing only the context would do all the necessary cleanup and we can drop h.Close() altogether?

@smlx
Copy link

smlx commented Apr 11, 2023

Maybe closing only the context would do all the necessary cleanup and we can drop h.Close() altogether?

That might be true, but the Close() function in the piv-go package is documented to "release the connection to the smart card". So changing that behaviour would be a breaking change.

@9ary
Copy link
Author

9ary commented Apr 11, 2023

I think you misunderstood me. yk.ctx is the SCard context, as in, the connection to pcscd. It is opened with SCardEstablishContext, and closed with SCardReleaseContext.
yk.h is the connection to the card, opened with SCardConnect, and closed with SCardDisconnect.

I am pretty sure that calling SCardDisconnect after SCardReleaseContext is incorrect. According to the pcsclite documentation:

Destroys a communication context to the PC/SC Resource Manager.

This must be the last function called in a PC/SC application.

What I am unsure about is whether this will cleanly shut down the card connection. My intuition says yes, but I would still be cautious. Even then, why would closing the context without disconnecting from the card first solve our problem?

MSDN is slightly different here (https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardreleasecontext):

The SCardReleaseContext function closes an established resource manager context, freeing any resources allocated under that context, including SCARDHANDLE objects and memory allocated using the SCARD_AUTOALLOCATE length designator.

I'm still trying to understand what's going on here.

pcsclite may not be at fault, however it should probably at least handle LIBUSB_TRANSFER_NO_DEVICE to get rid of that kernel warning about using an interface without claiming it.

@LudovicRousseau
Copy link
Owner

Calling SCardDisconnect() after SCardReleaseContext() should return the, expected, error SCARD_E_INVALID_HANDLE (I just checked).

The CCID driver does not do anything with the libusb error LIBUSB_TRANSFER_NO_DEVICE when returned on the interrupt pipe. The error is just logged. https://github.com/LudovicRousseau/CCID/blob/master/src/ccid_usb.c#L1544

What is very strange is that the next libusb call works fine with no error:

00000006 ccid_usb.c:1545:InterruptRead() InterruptRead (1/69): LIBUSB_TRANSFER_NO_DEVICE
00400118 ifdhandler.c:1955:IFDHICCPresence() usb:1050/0406:libudev:1:/dev/bus/usb/001/069 (lun: 0)
00000013 -> 000000 65 00 00 00 00 00 1A 00 00 00 
00000314 <- 000000 81 00 00 00 00 00 1A 00 00 00 
00000008 ifdhandler.c:2081:IFDHICCPresence() Card present

I would expect the kernel/libusb to also return LIBUSB_TRANSFER_NO_DEVICE. And this error would be handled correctly and the device declared removed by PC/SC.

@9ary
Copy link
Author

9ary commented Apr 11, 2023

https://libusb.sourceforge.io/api-1.0/libusb_caveats.html
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#gafee9c4638f1713ca5faa867948878111

If I understand the libusb docs correctly, this might be expected behavior. The kernel considers this event a reset because the descriptor is identical (even across different tokens, because they set the serial number to 0). On top of that, there's basically no way to get notified of a bus reset from userspace.

As far as I understand, a bus reset actually involves re-enumeration, but the kernel simply does not expose it the same way as unplugging and replugging the device.

Yubikeys seem happy to continue working after a bus reset, and in fact it seems if they haven't lost power then the "card's" state remains intact so clients don't even notice that anything happened. I have no idea how other readers would behave.

I think we have two potential bugs then:

  • Unplugging the token during sleep while yubikey-agent has an idle card connection and transaction results in an error on the next card access, but closing the connection doesn't work properly for some reason, blocking all clients from accessing the card. Restarting pcscd or yubikey-agent clears this situation and the card can be used again. Closing the connection before sleep also avoids the problem entirely.
  • pcscd may need to handle LIBUSB_TRANSFER_NO_DEVICE to simulate a reader disconnect. This would probably be flaky without proper udev events though. Maybe the unbind event is useful, or the kernel could be improved to notify userspace properly. This could also be a wontfix.

@LudovicRousseau
Copy link
Owner

Good news, I am able to reproduce the problem on my side.

But I am not yet sure how to handle the issue. Maybe the good answer is to report a card reset. This is, in fact, what happens when the computer shuts down the power of the USB reader/token.
The application would then have to reconnect to the card and pcscd+libccid will renegotiate communication parameters.

You can use reset_card.py to generate a card reset and see what happens for your application.

@9ary
Copy link
Author

9ary commented Apr 12, 2023

You can use reset_card.py to generate a card reset and see what happens for your application.

If I run this while yubikey-agent is holding the card connection, I get a sharing violation error.

$ ./reset_card.py
PC/SC Readers: ['Yubico YubiKey FIDO+CCID 00 00']
Using reader: Yubico YubiKey FIDO+CCID 00 00
Traceback (most recent call last):
  File "/home/streetwalrus/./reset_card.py", line 40, in <module>
    raise BaseSCardException(hresult)
smartcard.pcsc.PCSCExceptions.BaseSCardException: scard exception: Sharing violation. (0x8010000B)

If I make yk-agent release the card first, then run that script, it works fine:

$ ./reset_card.py
PC/SC Readers: ['Yubico YubiKey FIDO+CCID 00 00']
Using reader: Yubico YubiKey FIDO+CCID 00 00
reader: Yubico YubiKey FIDO+CCID 00 00
state: 0x34
protocol: 2
atr: 3B FD 13 00 00 81 31 FE 15 80 73 C0 21 C0 57 59 75 62 69 4B 65 79 40
reset using SCardDisconnect

After that, attempting to use the agent again results in another pin prompt and things work fine.

For the record, although I believe it's shown in the logs: before any operation, yubikey-agent tries to get the attestation certificate from the card; if that fails, it disconnects, releases the context, and tries to connect again from scratch. For some reason, it's this last part that fails. At this point, other clients are also unable to connect until I kill or restart yubikey-agent.

@LudovicRousseau
Copy link
Owner

Thanks for the comment.
Maybe reporting a card reset is not a good idea. If an application has an exclusive connection to a card then a card reset can't happen. So the application may be confused.

Another idea is to report a card removal/insertion. It is always possible to remove a card, even in the middle of an exclusive transaction. This behaviour can be done inside libccid itself without modifying pcsc-lite, so this is a good point.

But maybe that a yubikey application will be surprised to see a "card" removal event for a USB token if the token itself has not been removed.

@9ary
Copy link
Author

9ary commented Apr 12, 2023

For what it's worth, the application doesn't listen for hotplug events. It simply enumerates readers anytime it wants to connect. I've been considering writing my own replacement for it which would use hotplug events and support multiple cards. In that case, a removal and insertion event would be useful in order to re-enumerate the certificates on the card.

I think properly detecting the resets is going to be difficult though, as far as I can tell there's no way to do that from userspace unless there's an open connection to the card.

@LudovicRousseau
Copy link
Owner

@9ary I have a question.
With "always-on USB" enabled, when you wake up the laptop do you also get this line in pcscd logs:

00000006 ccid_usb.c:1545:InterruptRead() InterruptRead (1/69): LIBUSB_TRANSFER_NO_DEVICE

In my case on a Lenovo laptop I have "always-on USB" enabled but I can see that the smart card reader is powered off (and also the smart card of course) on sleep.

@9ary
Copy link
Author

9ary commented Apr 12, 2023

Yes indeed, my laptop is a Lenovo as well, and I am seeing the same thing:

10463240 ccid_usb.c:1532:InterruptRead() after (0) (5)
00000021 ccid_usb.c:1545:InterruptRead() InterruptRead (1/90): LIBUSB_TRANSFER_NO_DEVICE
00400091 ifdhandler.c:1955:IFDHICCPresence() usb:1050/0406:libudev:1:/dev/bus/usb/001/090 (lun: 0)
00000030 -> 000000 65 00 00 00 00 00 1A 00 00 00 
00000436 <- 000000 81 00 00 00 00 00 1A 00 00 00 
00000018 ifdhandler.c:2081:IFDHICCPresence() Card present
00000011 ifdhandler.c:318:IFDHPolling() usb:1050/0406:libudev:1:/dev/bus/usb/001/090 (lun: 0) 600000 ms
00000010 ccid_usb.c:1486:InterruptRead() before (0), timeout: 600000 ms

In my case on a Lenovo laptop I have "always-on USB" enabled but I can see that the smart card reader is powered off (and also the smart card of course) on sleep.

What I don't get is that some/all state is preserved in my case, are you sure this isn't just USB suspend? Maybe the reader you're testing with powers off the card in that state.

@smlx
Copy link

smlx commented Apr 13, 2023

I think you misunderstood me. yk.ctx is the SCard context, as in, the connection to pcscd. It is opened with SCardEstablishContext, and closed with SCardReleaseContext.
yk.h is the connection to the card, opened with SCardConnect, and closed with SCardDisconnect.

Ah yes of course, thank you.

What I am unsure about is whether this will cleanly shut down the card connection. My intuition says yes, but I would still be cautious. Even then, why would closing the context without disconnecting from the card first solve our problem?

I was thinking that maybe some invalid state inside the context is unaware of the card reset caused by unplugging the card, which has severed the connection to the card without a call to SCardDisconnect. No idea if that is plausible?

Calling SCardDisconnect() after SCardReleaseContext() should return the, expected, error SCARD_E_INVALID_HANDLE (I just checked).

Okay, thanks for confirming. And yes, I see this with my patch.

In that case, the fact that my patch works at all would seem to indicate that the bug can be triggered by calling SCardDisconnect(h, SCARD_LEAVE_CARD) on the handle of a card which has been unplugged (and therefore reset) during suspend?

@9ary
Copy link
Author

9ary commented Apr 13, 2023

I was thinking that maybe some invalid state inside the context is unaware of the card reset caused by unplugging the card, which has severed the connection to the card without a call to SCardDisconnect. No idea if that is plausible?

I think the missing piece is attempting to use the card even though the connection wasn't initialized. If you send sighup to yk-agent immediately after wakeup, then things work.

@9ary
Copy link
Author

9ary commented Apr 13, 2023

I think I got to the bottom of this problem.

I did a google search for "Linux unplug usb during suspend" and this was the first result: https://www.kernel.org/doc/html/v4.13/driver-api/usb/persist.html.

The kernel includes a feature called USB-persist. It tries to work around these issues by allowing the core USB device data structures to persist across a power-session disruption.

It works like this. If the kernel sees that a USB host controller is not in the expected state during resume (i.e., if the controller was reset or otherwise had lost power) then it applies a persistence check to each of the USB devices below that controller for which the “persist” attribute is set. It doesn’t try to resume the device; that can’t work once the power session is gone. Instead it issues a USB port reset and then re-enumerates the device. (This is exactly the same thing that happens whenever a USB device is reset.) If the re-enumeration shows that the device now attached to that port has the same descriptors as before, including the Vendor and Product IDs, then the kernel continues to use the same device structure. In effect, the kernel treats the device as though it had merely been reset instead of unplugged.

Well, that explains a lot. Sure enough:

$ cat /sys/bus/usb/devices/1-2/power/persist 
1

After setting this to 0, I can now see a new device in dmesg after wakeup, and yubikey-agent simply prompts me for my pin again, as if I had unplugged the token while the system was awake.

So this is the root cause. Userspace is doing nothing wrong, it's a kernel feature that's messing things up. A udev rule (or a kernel-side quirk?) should take care of this.

Edit: despite what that documentation page seems to imply, USB persist has been on by default for a while. https://github.com/torvalds/linux/blob/de4664485abbc0529b1eec44d0061bbfe58a28fb/drivers/usb/core/Kconfig#L20-L33

@9ary
Copy link
Author

9ary commented Apr 13, 2023

I came up with the following udev rule. It matches any USB device with a smartcard class interface and disables persistence.

ACTION=="add", SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:0b0000:*", ATTR{power/persist}="0"

@LudovicRousseau if this looks good to you, I can send a PR to systemd to add this to the standard configuration. Or maybe it's better for pcsclite to ship this rule instead?

That said, I find it shocking that this standards non-compliant hack is enabled by default in the kernel, and more importantly, for every device rather than just those it's trying to "fix" (i.e., mass storage devices). I wonder if changing this would be acceptable or if it'd be considered breaking userspace.

@LudovicRousseau
Copy link
Owner

Thanks @9ary for your research about USB_DEFAULT_PERSIST. Yes, it explains a lot of the strange behavior.
No need to contact systemd maintainer. The CCID driver already provides a udev.rules file for other USB settings https://github.com/LudovicRousseau/CCID/blob/master/src/92_pcscd_ccid.rules
I can add a line to disable power/persist.

Before I saw your comments I worked on a patch for the CCID driver:

diff --git a/src/ccid_usb.c b/src/ccid_usb.c
index 92b8982..1e76aab 100644
--- a/src/ccid_usb.c
+++ b/src/ccid_usb.c
@@ -1540,6 +1540,11 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
 		case LIBUSB_TRANSFER_TIMED_OUT:
 			break;
 
+		case LIBUSB_TRANSFER_NO_DEVICE:
+			DEBUG_COMM("Simulate card removal");
+			get_ccid_slot(reader_index)->bPowerFlags = MASK_POWERFLAGS_PUP;
+			break;
+
 		default:
 			/* if libusb_interrupt_transfer() times out we get EILSEQ or EAGAIN */
 			DEBUG_COMM4("InterruptRead (%d/%d): %s",

You can try it to see if it solves the problem.

@LudovicRousseau
Copy link
Owner

I am not sure ATTR{power/persist} works as expected.

I updated the CCID udev rule file (installed in /lib/udev/rules.d/92-libccid.rules on my Debian system).

diff --git a/src/92_pcscd_ccid.rules b/src/92_pcscd_ccid.rules
index 0078017..79ca431 100644
--- a/src/92_pcscd_ccid.rules
+++ b/src/92_pcscd_ccid.rules
@@ -11,6 +11,10 @@ ENV{DEVTYPE}!="usb_device", GOTO="pcscd_ccid_rules_end"
 # Kobil mIDentity
 ATTRS{idVendor}=="0d46", ATTRS{idProduct}=="4081", RUN+="/usr/sbin/Kobil_mIDentity_switch"
 
+# disable USB persist on suspend
+# https://www.kernel.org/doc/html/v4.13/driver-api/usb/persist.html
+ENV{ID_USB_INTERFACES}==":0b0000:", TEST=="power/persist", ATTR{power/persist}="0"
+
 # Keep USB autosuspend off for the C3PO LTC31 v1 SmartCard Reader
 ATTR{idVendor}=="0783", ATTR{idProduct}=="0003", GOTO="pcscd_ccid_rules_end"

Now I have:

$ cat /sys/bus/usb/devices/usb3/3-9/manufacturer 
Gemplus
$ cat /sys/bus/usb/devices/usb3/3-9/power/persist 
0

But the USB reader is not disconnected/reconnected. It has the same device number (Device 007 here) after a suspend/resume:

$ lsusb -d 08e6:3437
Bus 003 Device 007: ID 08e6:3437 Gemalto (was Gemplus) GemPC Twin SmartCard Reader

The device number changes if, and only if, I unplug/replug the reader while the laptop is suspended.
ATTR{power/persist} only solves half of the problem :-(

Do you have the same beaviour on your side?

@9ary
Copy link
Author

9ary commented Apr 13, 2023

No need to contact systemd maintainer. The CCID driver already provides a udev.rules file for other USB settings https://github.com/LudovicRousseau/CCID/blob/master/src/92_pcscd_ccid.rules

Ah good, I was looking for this. It looks like the best place to put this rule indeed.

The device number changes if, and only if, I unplug/replug the reader while the laptop is suspended.
ATTR{power/persist} only solves half of the problem :-(

Do you have the same beaviour on your side?

Yes, it's the same for me. I believe this is working as intended, the kernel's USB persistence mechanism only kicks in when the device has been unplugged (in their terms, when the "power session" has been interrupted).

Is your reader losing state or powering the card off during suspend? In that case, I think it might be a buggy reader. The yubikey doesn't appear to be losing state in sleep mode, the connection/transaction survives sleep cycles no problem (I don't have to re-enter my PIN).

In fact, I've gone back and disabled always-on USB again, and it doesn't appear to be losing power during sleep, so I guess it was just me imagining things on that point. The problem only occurs when the token is unplugged, which is already annoying enough - unplugging it after closing the lid is a bit of a habit.

@9ary
Copy link
Author

9ary commented Apr 13, 2023

diff --git a/src/92_pcscd_ccid.rules b/src/92_pcscd_ccid.rules
index 0078017..79ca431 100644
--- a/src/92_pcscd_ccid.rules
+++ b/src/92_pcscd_ccid.rules
@@ -11,6 +11,10 @@ ENV{DEVTYPE}!="usb_device", GOTO="pcscd_ccid_rules_end"
 # Kobil mIDentity
 ATTRS{idVendor}=="0d46", ATTRS{idProduct}=="4081", RUN+="/usr/sbin/Kobil_mIDentity_switch"
 
+# disable USB persist on suspend
+# https://www.kernel.org/doc/html/v4.13/driver-api/usb/persist.html
+ENV{ID_USB_INTERFACES}==":0b0000:", TEST=="power/persist", ATTR{power/persist}="0"
+
 # Keep USB autosuspend off for the C3PO LTC31 v1 SmartCard Reader
 ATTR{idVendor}=="0783", ATTR{idProduct}=="0003", GOTO="pcscd_ccid_rules_end"

I was looking at this again and I realized you removed the wildcards. This fails to match yubikeys because they are composite devices: in addition to the smartcard/CCID interface, they also have HID interfaces for the FIDO2 and "OTP" functions.

@smlx
Copy link

smlx commented Apr 14, 2023

FWIW, this rule fixes the problem for me:

ENV{ID_USB_INTERFACES}=="*:0b0000:*", TEST=="power/persist", ATTR{power/persist}="0"

Suspend, unplug, replug, resume now works correctly.

@LudovicRousseau
Copy link
Owner

@9ary do you confirm that the udev rule also fixes all your problems?

The patch I proposed in #154 (comment) will not work with a yubikey. But I may use it for a reader with a real smart card.

@9ary
Copy link
Author

9ary commented Apr 14, 2023

@9ary do you confirm that the udev rule also fixes all your problems?

The rule I proposed does, the one @smlx posted should also work fine, though I haven't tested it.

The patch I proposed in #154 (comment) will not work with a yubikey. But I may use it for a reader with a real smart card.

That's fair enough. If I only cared about yubikeys, I probably would've matched on the VID, but I wanted to make it generic since this problem most likely affects all CCID devices.

LudovicRousseau added a commit to LudovicRousseau/CCID that referenced this issue Apr 22, 2023
The Linux kernel tries to keep the USB connection during suspend/resume
cycles. This is a bad idea if the reader/token is unpluged while the
computer is suspended.
More documentation is available at
https://docs.kernel.org/driver-api/usb/persist.html

If a CCID reader (including a composite one) is disconnected and
reconnected while the computer is suspended then the USB device will be
considered removed and connected again.
PC/SC applications will have to reconnect.

Closes: LudovicRousseau/PCSC#154
"USB resets leave the reader connection in a broken state #154"
LudovicRousseau added a commit to LudovicRousseau/CCID that referenced this issue Apr 22, 2023
The Linux kernel tries to keep the USB connection during suspend/resume
cycles. This is a bad idea if the reader/token is unpluged while the
computer is suspended.
More documentation is available at
https://docs.kernel.org/driver-api/usb/persist.html

If a CCID reader (including a composite one) is disconnected and
reconnected while the computer is suspended then the USB device will be
considered removed and connected again.
PC/SC applications will have to reconnect.

Closes: LudovicRousseau/PCSC#154
"USB resets leave the reader connection in a broken state #154"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants