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):
|
class PamResponse(Structure):
|
||||||
"""PAM response structure (pam_response)."""
|
"""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_ = [
|
_fields_ = [
|
||||||
("resp", c_char_p),
|
("resp", c_void_p),
|
||||||
("resp_retcode", c_int),
|
("resp_retcode", c_int),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -56,6 +58,19 @@ def _get_libpam() -> ctypes.CDLL:
|
|||||||
return ctypes.CDLL(pam_path)
|
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:
|
def _make_conv_func(password: str) -> PamConvFunc:
|
||||||
"""Create a PAM conversation callback that provides the password."""
|
"""Create a PAM conversation callback that provides the password."""
|
||||||
|
|
||||||
@ -65,13 +80,19 @@ def _make_conv_func(password: str) -> PamConvFunc:
|
|||||||
resp: POINTER(POINTER(PamResponse)),
|
resp: POINTER(POINTER(PamResponse)),
|
||||||
appdata_ptr: c_void_p,
|
appdata_ptr: c_void_p,
|
||||||
) -> int:
|
) -> int:
|
||||||
# Allocate response array
|
# PAM expects malloc'd memory — it will free() the responses and resp strings
|
||||||
response = (PamResponse * num_msg)()
|
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):
|
for i in range(num_msg):
|
||||||
response[i].resp = password.encode("utf-8")
|
# strdup allocates with malloc, which PAM can safely free()
|
||||||
response[i].resp_retcode = 0
|
resp_ptr[i].resp = libc.strdup(password.encode("utf-8"))
|
||||||
# PAM expects malloc'd memory; ctypes handles this via the array
|
resp_ptr[i].resp_retcode = 0
|
||||||
resp[0] = ctypes.cast(response, POINTER(PamResponse))
|
|
||||||
|
resp[0] = resp_ptr
|
||||||
return PAM_SUCCESS
|
return PAM_SUCCESS
|
||||||
|
|
||||||
return PamConvFunc(_conv)
|
return PamConvFunc(_conv)
|
||||||
|
|||||||
@ -181,7 +181,10 @@ class LockscreenWindow(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
# Use GLib thread pool to avoid blocking GTK mainloop
|
# Use GLib thread pool to avoid blocking GTK mainloop
|
||||||
def _auth_thread() -> bool:
|
def _auth_thread() -> bool:
|
||||||
result = _do_auth()
|
try:
|
||||||
|
result = _do_auth()
|
||||||
|
except Exception:
|
||||||
|
result = False
|
||||||
GLib.idle_add(_on_auth_done, result)
|
GLib.idle_add(_on_auth_done, result)
|
||||||
return GLib.SOURCE_REMOVE
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user