Skip to content

Commit ca22c48

Browse files
authored
Fix StatefulSignature segfault when liboqs lacks STFL keygen support (#144)
Closes #121. liboqs typedefs OQS_SIG_STFL to OQS_SIG when built without OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN, so OQS_SIG_STFL_new() returns a struct with an incompatible layout. StatefulSignature.__init__ then segfaulted reading alg_version as a c_char_p over non-pointer data. Detect the build mode before touching the struct: prefer the upstream OQS_SIG_STFL_keygen_and_sign_supported() API (open-quantum-safe/liboqs#2434); fall back to a struct-layout probe (safe c_char_p read at offset 8) for older liboqs. Also add keypair_cb/sign_cb NULL guards in generate_keypair and sign as defense in depth, and document the build-flag requirement in the README. Signed-off-by: Douglas Stebila <dstebila@uwaterloo.ca>
1 parent f956587 commit ca22c48

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ seed using `KeyEncapsulation.generate_keypair_seed()`.
183183
The files in `examples/` demonstrate the wrapper's API. Support for alternative
184184
RNGs is provided via the `randombytes_*()` functions.
185185

186+
Note: `StatefulSignature` requires liboqs to be built with
187+
`-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON` in addition to
188+
`-DOQS_ENABLE_SIG_STFL_LMS=ON` and/or `-DOQS_ENABLE_SIG_STFL_XMSS=ON`. Without
189+
that flag, instantiating `StatefulSignature` raises `RuntimeError` (the public
190+
C ABI for stateful signatures is only available when the flag is set).
191+
186192
The liboqs-python project should be in the `PYTHONPATH`. To ensure this on
187193
UNIX-like systems, execute
188194

oqs/oqs.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,32 @@ def _check_alg(alg_name: str) -> None:
962962
raise MechanismNotEnabledError(alg_name, enabled=sup)
963963

964964

965+
def _stfl_keygen_and_sign_supported(alg_name: str) -> bool:
966+
"""Return True if liboqs supports stateful keygen/sign for `alg_name`.
967+
968+
Prefer the upstream API `OQS_SIG_STFL_keygen_and_sign_supported` (added in
969+
liboqs >= 0.16.0). If it's not present in the loaded library, fall back to a
970+
struct-layout probe: when liboqs is built without the HAZARDOUS flag, its
971+
public header types `OQS_SIG_STFL` as `OQS_SIG`, so the pointer at offset 8
972+
of the returned struct is `alg_version` (e.g. a fixed RFC URL) instead of
973+
`method_name`. Both layouts have a `const char *` at offset 8, so the read
974+
itself is safe in either mode.
975+
"""
976+
fn = getattr(native(), "OQS_SIG_STFL_keygen_and_sign_supported", None)
977+
if fn is not None:
978+
fn.restype = ct.c_int
979+
fn.argtypes = []
980+
return bool(fn())
981+
982+
sig = native().OQS_SIG_STFL_new(ct.create_string_buffer(alg_name.encode()))
983+
if not sig:
984+
return False
985+
try:
986+
return sig.contents.method_name == alg_name.encode()
987+
finally:
988+
native().OQS_SIG_STFL_free(sig)
989+
990+
965991
class StatefulSignature(ct.Structure):
966992
"""
967993
An OQS StatefulSignature wraps native/C liboqs OQS_SIG structs.
@@ -1005,6 +1031,20 @@ def __init__(self, alg_name: str, secret_key: Optional[bytes] = None) -> None:
10051031
super().__init__()
10061032

10071033
_check_alg(alg_name)
1034+
1035+
# liboqs's public header defines `OQS_SIG_STFL` as `OQS_SIG` when built without
1036+
# OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN, so OQS_SIG_STFL_new
1037+
# returns a struct with a different layout — reading it via this wrapper's
1038+
# _fields_ segfaults. Detect the build mode before touching the struct.
1039+
if not _stfl_keygen_and_sign_supported(alg_name):
1040+
msg = (
1041+
f"Stateful signatures are not fully supported by the loaded liboqs: "
1042+
f"liboqs must be built with "
1043+
f"-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON "
1044+
f"to use StatefulSignature for {alg_name}."
1045+
)
1046+
raise RuntimeError(msg)
1047+
10081048
self._sig = native().OQS_SIG_STFL_new(ct.create_string_buffer(alg_name.encode()))
10091049
if not self._sig:
10101050
msg = f"Could not allocate OQS_SIG_STFL for {alg_name}"
@@ -1080,13 +1120,22 @@ def generate_keypair(self) -> bytes:
10801120
msg = "Keypair already generated, call free() to release the secret key"
10811121
raise ValueError(msg)
10821122

1123+
sig_struct = self._sig.contents
1124+
if not sig_struct.keypair_cb:
1125+
msg = (
1126+
f"Stateful keypair generation is not available for "
1127+
f"{self.method_name.decode()}. liboqs must be built with "
1128+
f"-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON "
1129+
f"to enable stateful signature key generation."
1130+
)
1131+
raise RuntimeError(msg)
1132+
10831133
self._secret_key = native().OQS_SIG_STFL_SECRET_KEY_new(self.method_name)
10841134
if not self._secret_key:
10851135
msg = "Could not allocate OQS_SIG_STFL_SECRET_KEY"
10861136
raise RuntimeError(msg)
10871137
self._attach_store_cb()
10881138

1089-
sig_struct = self._sig.contents
10901139
pk_buf = ct.create_string_buffer(sig_struct.length_public_key)
10911140

10921141
rc = native().OQS_SIG_STFL_keypair(self._sig, pk_buf, self._secret_key)
@@ -1111,6 +1160,14 @@ def sign(self, message: bytes) -> bytes:
11111160
if self._secret_key is None:
11121161
msg = "Secret key not initialised – call generate_keypair() first"
11131162
raise RuntimeError(msg)
1163+
if not self._sig.contents.sign_cb:
1164+
msg = (
1165+
f"Stateful signing is not available for "
1166+
f"{self.method_name.decode()}. liboqs must be built with "
1167+
f"-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON "
1168+
f"to enable stateful signature signing."
1169+
)
1170+
raise RuntimeError(msg)
11141171
c_signature = ct.create_string_buffer(self.length_signature)
11151172
c_signature_len = ct.c_size_t(self.length_signature)
11161173
msg_buf = ct.create_string_buffer(message, len(message))

0 commit comments

Comments
 (0)