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

Handle short-lived removals of reader in reader driver for PC/SC #2803

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

xhanulik
Copy link
Contributor

As discussed in #2740, the refresh_attributes() function in reader-pcsc.c does not correctly handle the state when the reader is removed and inserted between two subsequent calls to refresh_attributes(). In that case, the PCSC SCardGetStatusChange() returns SCARD_E_TIMEOUT, handled as the card is present and did not change.

In #2740 (comment), there was proposed to use one more call to SCardBeginTransaction(), it seems to be also working with SCardStatus() - when timeout is received, the invalid card handle should signalize recently removed reader and SC_READER_CARD_CHANGED is set.

The flag SC_READER_CARD_CHANGED is also set when SCARD_E_UNKNOWN_READER is first received from SCardGetStatusChange().

The changes regarding slot handling remove the infinite loop from card_detect() when SC_READER_CARD_CHANGED is repeatedly acquired. It happened when the reader was removed & inserted back, so the timeout was reached, but no reconnection between the refresh_attributes() in pcsc-reader.c happens - the card handle is invalid, so the flag about changed is repeatedly set. This should probably be handled differently - any insight would be appreciated.

The changes were tested with Yubikey according to #2740 (comment).

@Jakuje
Copy link
Member

Jakuje commented Jun 22, 2023

Nits:

  • Typo in the commit message: the change is not reported. Tthe card handle should in f16e698
  • Typo in the commit message: reader-pscs: in d8e661d -- I think this also needs a bit more explanation why this is done (to avoid cycling somewhere?)

Otherwise looks reasonable.

@xhanulik
Copy link
Contributor Author

  • Typo in the commit message: reader-pscs: in d8e661d -- I think this also needs a bit more explanation why this is done (to avoid cycling somewhere?)

This was done only to reflect the change in the flags. Before this change, when the reader was removed, the reader->flags in refresh_attributes() was updated from 0x00000001 (SC_READER_CARD_PRESENT) to 0x00000020 (SC_READER_REMOVED). However, when the reader was still removed by another call to refresh_attributes(), the flags remained 0x00000020 (SC_READER_REMOVED).

Now the flags should change as 0x00000001 (SC_READER_CARD_PRESENT) → 0x00000022 (SC_READER_REMOVED | SC_READER_CARD_CHANGED) and then 0x00000022 (SC_READER_REMOVED | SC_READER_CARD_CHANGED) → 0x00000020 (SC_READER_REMOVED).

It is reflected in the commit message now. Thanks for the review!

@xhanulik xhanulik marked this pull request as ready for review June 28, 2023 09:32
@frankmorgner
Copy link
Member

Ludovic stated that

I note that, on Windows, the SCardBeginTransaction() does not fail after reader removal/re-insertion. So you should also check the result of the next PC/SC call.
I have not checked under macOS.

So if I understand correctly, then using SCardStatus for removal/reinsertion may only work in pcsc-lite, right?

@xhanulik
Copy link
Contributor Author

So if I understand correctly, then using SCardStatus for removal/reinsertion may only work in pcsc-lite, right?

I understood it as SCardBeginTransaction would not detect it; however, I'm not sure about SCardStatus - unfortunately, I cannot test it myself.

@xhanulik
Copy link
Contributor Author

I also measured possible time overhead regarding the changes in PCSC calling. Using the time command and pkcs11-tool --test, I did measurements for the PIV card, when the time increased on average (4 tries) from 46.352s to 46.576s and IDPrime card, when the increase was approximately from 7.382s to 7.445s.

src/libopensc/reader-pcsc.c Show resolved Hide resolved
@Jakuje
Copy link
Member

Jakuje commented Jul 11, 2023

@LudovicRousseau can you have a look if this looks reasonable to you?

/* Make sure to preserve the CARD_PRESENT flag if the reader was
* reattached and we called the refresh_attributes too recently */
if (priv->reader_state.dwEventState & SCARD_STATE_PRESENT) {
reader->flags |= SC_READER_CARD_PRESENT;
}

rv = priv->gpriv->SCardStatus(priv->pcsc_card, NULL, &readers_len, &cstate, &prot, atr, &atr_len);
if (priv->pcsc_card != 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can test if priv->pcsc_card != 0 before the call to SCardStatus() to note waste CPU cycles?

A hCard set to 0 is invalid for pcsc-lite https://github.com/LudovicRousseau/PCSC/blob/master/src/winscard.c#L1254-L1256. But I don't know for the other WinSCard implementations (Windows and macOS).

@LudovicRousseau
Copy link
Member

No objection. But I am not an OpenSC internals expert.

I suggest to make some tests on all the platforms (GNU/Linux, macOS, Windows, etc.) supported by OpenSC to be sure to capture all the different behaviors of SCardStatus(). I would use https://github.com/LudovicRousseau/PCSC/blob/master/UnitaryTests/SCardStatus.py for that.

When the reader is removed and inserted back between
two consecutive callings to SCardGetStatusChange(),
the change is not reported. The card handle should
be invalidated, which is detected by SCardStatus.

When no reconnection is done afterwards,
SCardGetStatusChange() will still return timeout
and ard handle will be invalid.
When the removed reader is detected for the first time,
set SC_READER_CARD_CHANGED denoting change. Remove
the flag when the condition is hit again.

Before this change, when reader was removed
the reader->flags in refresh_attributes() was updated
from 0x00000001 (SC_READER_CARD_PRESENT) to 0x00000020
(SC_READER_REMOVED). However, when the reader is still
removed by another call to refresh_attributes(),
the flags remained as 0x00000020 (SC_READER_REMOVED).

Now the flags should change with SC_READER_CARD_CHANGED.
When SCardGetStatusChange returns value successfully,
remove also SC_READER_REMOVED before setting the current state.
When reader is removed and inserted back between two
consecutive calls to PCSC SCardGetStatusChange() function,
and no reconnection is done, the status will remain
SC_READER_CARD_CHANGED.
The card_detect() can detect removed reader,
it calls card_removed(), but card_detect_all()
did not remove relation between slot and reader.
Copy link
Member

@Jakuje Jakuje left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nits and format, but I think this is good to go.

Comment on lines +378 to +379
DWORD readers_len = 0, cstate = 0, prot, atr_len = SC_MAX_ATR_SIZE;
unsigned char atr[SC_MAX_ATR_SIZE];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would probably move these variables into the inner block where you use them.

* remove only relation between reader and slot. */
if (reader->flags & SC_READER_REMOVED) {
for (j = 0; j<list_size(&virtual_slots); j++) {
sc_pkcs11_slot_t *slot = (sc_pkcs11_slot_t *) list_get_at(&virtual_slots, j);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are few minor formatting suggests from clang-format, that I would like to bring in too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants