fix(lockscreen): add setuid unix_chkpwd helper for PAM authentication in lockscreen_auth_service.py
This commit is contained in:
parent
dac3c03e44
commit
24ca1908bc
@ -4,33 +4,60 @@ import os
|
|||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
import getpass
|
import getpass
|
||||||
|
import subprocess
|
||||||
|
|
||||||
libpam = None
|
def verify_via_chkpwd(username, password):
|
||||||
try:
|
"""Uses setuid unix_chkpwd helper for unprivileged Linux desktop password verification."""
|
||||||
|
chkpwd_paths = [
|
||||||
|
'/sbin/unix_chkpwd',
|
||||||
|
'/usr/sbin/unix_chkpwd',
|
||||||
|
'/usr/libexec/unix_chkpwd',
|
||||||
|
'/usr/lib/security/unix_chkpwd',
|
||||||
|
'/usr/lib/chkpwd/unix_chkpwd'
|
||||||
|
]
|
||||||
|
|
||||||
|
chkpwd_bin = None
|
||||||
|
for p in chkpwd_paths:
|
||||||
|
if os.path.exists(p):
|
||||||
|
chkpwd_bin = p
|
||||||
|
break
|
||||||
|
|
||||||
|
if not chkpwd_bin:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[chkpwd_bin, username, 'nullhelper'],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
pwd_bytes = password.encode('utf-8') + b'\x00'
|
||||||
|
proc.communicate(input=pwd_bytes, timeout=3)
|
||||||
|
return (proc.returncode == 0)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def verify_via_libpam(username, password):
|
||||||
|
"""Uses libpam C API for PAM service authentication."""
|
||||||
|
try:
|
||||||
libpam_path = ctypes.util.find_library('pam') or 'libpam.so.0'
|
libpam_path = ctypes.util.find_library('pam') or 'libpam.so.0'
|
||||||
libpam = ctypes.CDLL(libpam_path)
|
libpam = ctypes.CDLL(libpam_path)
|
||||||
except Exception:
|
libc = ctypes.CDLL(None)
|
||||||
libpam = None
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
libc = ctypes.CDLL(None)
|
class PamMessage(ctypes.Structure):
|
||||||
|
|
||||||
class PamMessage(ctypes.Structure):
|
|
||||||
_fields_ = [('msg_style', ctypes.c_int), ('msg', ctypes.c_char_p)]
|
_fields_ = [('msg_style', ctypes.c_int), ('msg', ctypes.c_char_p)]
|
||||||
|
|
||||||
class PamResponse(ctypes.Structure):
|
class PamResponse(ctypes.Structure):
|
||||||
_fields_ = [('resp', ctypes.c_char_p), ('resp_retcode', ctypes.c_int)]
|
_fields_ = [('resp', ctypes.c_char_p), ('resp_retcode', ctypes.c_int)]
|
||||||
|
|
||||||
CONV_FUNC = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.POINTER(PamMessage)), ctypes.POINTER(ctypes.POINTER(PamResponse)), ctypes.c_void_p)
|
CONV_FUNC = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.POINTER(PamMessage)), ctypes.POINTER(ctypes.POINTER(PamResponse)), ctypes.c_void_p)
|
||||||
|
|
||||||
class PamConv(ctypes.Structure):
|
class PamConv(ctypes.Structure):
|
||||||
_fields_ = [('conv', CONV_FUNC), ('appdata_ptr', ctypes.c_void_p)]
|
_fields_ = [('conv', CONV_FUNC), ('appdata_ptr', ctypes.c_void_p)]
|
||||||
|
|
||||||
def verify_password(username, password):
|
|
||||||
if not password:
|
|
||||||
return False
|
|
||||||
if not libpam:
|
|
||||||
return False
|
|
||||||
|
|
||||||
pwd_bytes = password.encode('utf-8')
|
pwd_bytes = password.encode('utf-8')
|
||||||
|
|
||||||
def conv_cb(num_msg, msg, resp, appdata_ptr):
|
def conv_cb(num_msg, msg, resp, appdata_ptr):
|
||||||
@ -44,7 +71,8 @@ def verify_password(username, password):
|
|||||||
cb = CONV_FUNC(conv_cb)
|
cb = CONV_FUNC(conv_cb)
|
||||||
conv = PamConv(cb, None)
|
conv = PamConv(cb, None)
|
||||||
|
|
||||||
for service in ['system-auth', 'login', 'passwd', 'kde', 'sddm', 'gdm', 'su', 'shadow']:
|
for service in ['system-auth', 'system-local-login', 'system-login', 'login', 'passwd', 'kde', 'sddm', 'gdm', 'su', 'other']:
|
||||||
|
try:
|
||||||
pamh = ctypes.c_void_p()
|
pamh = ctypes.c_void_p()
|
||||||
res = libpam.pam_start(service.encode('utf-8'), username.encode('utf-8'), ctypes.byref(conv), ctypes.byref(pamh))
|
res = libpam.pam_start(service.encode('utf-8'), username.encode('utf-8'), ctypes.byref(conv), ctypes.byref(pamh))
|
||||||
if res == 0:
|
if res == 0:
|
||||||
@ -52,14 +80,31 @@ def verify_password(username, password):
|
|||||||
libpam.pam_end(pamh, auth_res)
|
libpam.pam_end(pamh, auth_res)
|
||||||
if auth_res == 0:
|
if auth_res == 0:
|
||||||
return True
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def verify_password(username, password):
|
||||||
|
if not password:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 1. Primary method for non-root desktop processes: unix_chkpwd
|
||||||
|
if verify_via_chkpwd(username, password):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 2. Fallback method: libpam API
|
||||||
|
if verify_via_libpam(username, password):
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) < 2:
|
password = ""
|
||||||
# Read from stdin
|
if len(sys.argv) >= 2:
|
||||||
password = sys.stdin.read().strip()
|
password = sys.argv[1]
|
||||||
else:
|
else:
|
||||||
password = sys.argv[1].strip()
|
password = sys.stdin.read().strip()
|
||||||
|
|
||||||
username = getpass.getuser()
|
username = getpass.getuser()
|
||||||
if len(sys.argv) >= 3:
|
if len(sys.argv) >= 3:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user