Fix PAM conversation segfault causing unrecoverable red lockscreen
ctypes auto-converts c_char_p return values to Python bytes, losing the original malloc'd pointer from strdup(). When PAM called free() on the response, it hit a ctypes-internal buffer instead — segfault. Use c_void_p for PamResponse.resp and strdup restype to preserve raw pointers. Also use calloc/strdup for proper malloc'd memory that PAM can safely free(). Add try/except in auth thread so UI stays interactive on PAM errors.
This commit is contained in:
parent
db05df36d4
commit
d0d390d0cb
@ -23,8 +23,10 @@ class PamMessage(Structure):
|
||||
class PamResponse(Structure):
|
||||
"""PAM response structure (pam_response)."""
|
||||
|
||||
# resp is c_void_p (not c_char_p) to preserve raw malloc'd pointers —
|
||||
# ctypes auto-converts c_char_p returns to Python bytes, losing the pointer.
|
||||
_fields_ = [
|
||||
("resp", c_char_p),
|
||||
("resp", c_void_p),
|
||||
("resp_retcode", c_int),
|
||||
]
|
||||
|
||||
@ -56,6 +58,19 @@ def _get_libpam() -> ctypes.CDLL:
|
||||
return ctypes.CDLL(pam_path)
|
||||
|
||||
|
||||
def _get_libc() -> ctypes.CDLL:
|
||||
"""Load and return the libc shared library."""
|
||||
libc_path = ctypes.util.find_library("c")
|
||||
if not libc_path:
|
||||
raise OSError("libc not found")
|
||||
libc = ctypes.CDLL(libc_path)
|
||||
libc.calloc.restype = c_void_p
|
||||
libc.calloc.argtypes = [ctypes.c_size_t, ctypes.c_size_t]
|
||||
libc.strdup.restype = c_void_p
|
||||
libc.strdup.argtypes = [c_char_p]
|
||||
return libc
|
||||
|
||||
|
||||
def _make_conv_func(password: str) -> PamConvFunc:
|
||||
"""Create a PAM conversation callback that provides the password."""
|
||||
|
||||
@ -65,13 +80,19 @@ def _make_conv_func(password: str) -> PamConvFunc:
|
||||
resp: POINTER(POINTER(PamResponse)),
|
||||
appdata_ptr: c_void_p,
|
||||
) -> int:
|
||||
# Allocate response array
|
||||
response = (PamResponse * num_msg)()
|
||||
# PAM expects malloc'd memory — it will free() the responses and resp strings
|
||||
libc = _get_libc()
|
||||
resp_array = libc.calloc(num_msg, ctypes.sizeof(PamResponse))
|
||||
if not resp_array:
|
||||
return PAM_AUTH_ERR
|
||||
|
||||
resp_ptr = ctypes.cast(resp_array, POINTER(PamResponse))
|
||||
for i in range(num_msg):
|
||||
response[i].resp = password.encode("utf-8")
|
||||
response[i].resp_retcode = 0
|
||||
# PAM expects malloc'd memory; ctypes handles this via the array
|
||||
resp[0] = ctypes.cast(response, POINTER(PamResponse))
|
||||
# strdup allocates with malloc, which PAM can safely free()
|
||||
resp_ptr[i].resp = libc.strdup(password.encode("utf-8"))
|
||||
resp_ptr[i].resp_retcode = 0
|
||||
|
||||
resp[0] = resp_ptr
|
||||
return PAM_SUCCESS
|
||||
|
||||
return PamConvFunc(_conv)
|
||||
|
||||
@ -181,7 +181,10 @@ class LockscreenWindow(Gtk.ApplicationWindow):
|
||||
|
||||
# Use GLib thread pool to avoid blocking GTK mainloop
|
||||
def _auth_thread() -> bool:
|
||||
result = _do_auth()
|
||||
try:
|
||||
result = _do_auth()
|
||||
except Exception:
|
||||
result = False
|
||||
GLib.idle_add(_on_auth_done, result)
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user