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

KeyboardInterrupt appears blocked by waitForSlotEvent() #117

Open
Torxed opened this issue Jun 24, 2024 · 5 comments
Open

KeyboardInterrupt appears blocked by waitForSlotEvent() #117

Torxed opened this issue Jun 24, 2024 · 5 comments
Assignees

Comments

@Torxed
Copy link

Torxed commented Jun 24, 2024

Your system information

  • Operating system used: Arch Linux
  • PyKCS11 version: 1.5.16
  • Python version: 3.12.3
  • PKCS#11 library used: opensc-pkcs11

Please describe your issue in as much detail as possible:

Using the events.py example, as referenced in #167, it appears to be working but blocking KeyboardInterrupt:

Traceback (most recent call last):
  File "/home/anton/test.py", line 15, in <module>
    slot = pkcs11.waitForSlotEvent()
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/PyKCS11/__init__.py", line 746, in waitForSlotEvent
    (rv, slot) = self.lib.C_WaitForSlotEvent(flags, tmp)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/PyKCS11/LowLevel.py", line 1808, in C_WaitForSlotEvent
    return _LowLevel.CPKCS11Lib_C_WaitForSlotEvent(self, flags, INOUT)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt

The above exception, appears to be blocked until systemctl stop pcscd.service is executed.

Steps for reproducing this issue:

import os
import PyKCS11

pkcs11 = PyKCS11.PyKCS11Lib()
if os.name == 'nt':
	pkcs11.load(r"C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll")
else:
	pkcs11.load("/usr/lib/opensc-pkcs11.so")

slots = pkcs11.getSlotList()
print(slots)

while True:
	try:
		slot = pkcs11.waitForSlotEvent()
		print(slot)
	except PyKCS11.PyKCS11Error as e:
		print("Error:", e)

Pressing Ctrl-C will result in:

anton@hotlap ~ $ python test.py
[0]
^C^C^C^C

Until pcscd.service is restarted, then:

anton@hotlap ~ $ python test.py
[0]
^C^C^C^CTraceback (most recent call last):
  File "/home/anton/test.py", line 15, in <module>
    slot = pkcs11.waitForSlotEvent()
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/PyKCS11/__init__.py", line 746, in waitForSlotEvent
    (rv, slot) = self.lib.C_WaitForSlotEvent(flags, tmp)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/PyKCS11/LowLevel.py", line 1808, in C_WaitForSlotEvent
    return _LowLevel.CPKCS11Lib_C_WaitForSlotEvent(self, flags, INOUT)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt

anton@hotlap ~ $
@LudovicRousseau
Copy link
Owner

The KeyboardInterrupt is blocked until the call to C_WaitForSlotEvent() returns to Python.
For example you can also insert or remove a token to get unblocked.

I don't know if it is possible to allow Ctrl-C from inside a C function called by Python.
Do you have an idea?

@Torxed
Copy link
Author

Torxed commented Jun 24, 2024

I believe the signal library would be able to hook the interrupt, from there you would need to break the C_WaitForSlotEvent() loop cleanly some how.

I haven't checked the low level C code yet, but could have a look :)

@LudovicRousseau
Copy link
Owner

The only way to interrupt a C_WaitForSlotEvent() is to call C_Finalize().
Maybe a signal handler could do that?

The problem is that the application that uses PyKCS11 may also want to get KeyboardInterrupt events.

@Torxed
Copy link
Author

Torxed commented Jun 24, 2024

My C is very rusty, but instinctively I think builtin Python could handle this:

The end users code:

import time

# `b.py` will emulate PyKCS11 here
# just for the sake of testing signal() handling
# import PyKCS11
import b

while True:
    time.sleep(0.25)

b.py:

import signal

_op_sigint = signal.getsignal(signal.SIGINT)

def signal_handler(sig, frame):
    print("lib.C_Finalize() here")
    _op_sigint(sig, frame)

signal.signal(signal.SIGINT, signal_handler)

The drawback is that the stacktrace will add upon the users actual position of the Ctrl-C:

anton@bigrigv2 ~ $ python test.py
^CTraceback (most recent call last):
  File "/home/anton/test.py", line 5, in <module>
    time.sleep(0.25)
  File "/home/anton/b.py", line 7, in signal_handler
    _op_sigint(sig, frame)
KeyboardInterrupt

Where it was actually called on time.sleep() not in b.py, but the stack trace will (I hope) stay intact, just having extra entries in the stack.
However, this is the smallest and most non-invasive handling I could think of.
As it preserves user-set signal handling, as well as defaults to built-in handler of the given signal.

Not sure how you would access the given PyKCS11Lib() instance, to reach self.lib.C_Finalize() tho.

@LudovicRousseau
Copy link
Owner

I don't think it is a good example code.
time.sleep() can be interrupted by Ctrl-C.

In your output I do not see the "lib.C_Finalize() here" message. Is the print() executed?

We need something that is not interruptible in Python.

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

2 participants