Document unsafe contracts and stop reusing poisoned state

This commit is contained in:
Samuel Onoja committed 2026-10-22 01:28:03 +01:00
1 parent a7fcd69897
commit f2e728b503
46 files changed
+377 -45

No files matched your search

+1
View File
@@ -296,6 +296,7 @@ pub fn load_wallpaper(path: &std::path::Path, blur: f32) -> Result<image::Handle
// Decode and resize scratch buffers have been dropped. Return their freed
// pages even when glibc would otherwise retain them in an arena.
#[cfg(all(target_os = "linux", target_env = "gnu"))]
// SAFETY: glibc malloc_trim is thread-safe and does not invalidate any live allocation.
unsafe {
libc::malloc_trim(0);
}
+15 -1
View File
@@ -26,7 +26,11 @@ struct Credentials {
const SUCCESS: c_int = 0;
const CONV_ERR: c_int = 19;
// Linux-PAM owns successful response allocations and frees them using free().
/// Linux-PAM owns successful response allocations and frees them using free().
///
/// # Safety
/// PAM must supply `count` readable message pointers, a writable output slot and
/// the live, exclusively borrowed Credentials supplied to the conversation.
unsafe extern "C" fn converse(
count: c_int,
messages: *mut *const Message,
@@ -36,6 +40,8 @@ unsafe extern "C" fn converse(
if count <= 0 || count > 32 || messages.is_null() || out.is_null() || data.is_null() {
return CONV_ERR;
}
// SAFETY: PAM supplies count message pointers and a writable response slot. data borrows
// Credentials for the synchronous conversation; successful calloc responses transfer to PAM.
unsafe {
*out = std::ptr::null_mut();
let credentials = &mut *(data as *mut Credentials);
@@ -94,6 +100,8 @@ unsafe extern "C" fn converse(
pub fn username() -> Result<String, String> {
// Never trust USER/LOGNAME for the account being unlocked.
// SAFETY: getpwuid_r receives initialized writable locals and the actual buffer length. Its
// returned name is read only after success, while the buffer remains alive.
unsafe {
let mut entry: libc::passwd = std::mem::zeroed();
let mut result = std::ptr::null_mut();
@@ -114,6 +122,8 @@ pub fn username() -> Result<String, String> {
pub fn available() -> bool {
std::path::Path::new("/etc/pam.d/ferese-lock").is_file()
// SAFETY: Only the system PAM library is loaded, and no symbols survive the library
// handle.
&& unsafe { libloading::Library::new("libpam.so.0") }.is_ok()
}
@@ -135,6 +145,8 @@ fn authenticate_with(user: &str, password: &str, config: Option<&CStr>) -> bool
password: secret,
password_prompts: 0,
};
// SAFETY: The typed symbols match the PAM ABI. Strings, conversation and credentials remain
// alive through pam_end, and the library outlives every symbol call.
unsafe {
let Ok(library) = libloading::Library::new("libpam.so.0") else {
return false;
@@ -232,6 +244,8 @@ mod tests {
};
let mut prompts = [&first as *const Message, &first as *const Message];
let mut responses = std::ptr::null_mut();
// SAFETY: The message array, response slot and Credentials are live and correctly typed
// for converse.
unsafe {
assert_eq!(
converse(
+1
View File
@@ -61,6 +61,7 @@ fn main() -> iced::Result {
});
// Password-bearing UI processes must not create core dumps.
// SAFETY: PR_SET_DUMPABLE takes an integer value and does not dereference caller memory.
unsafe {
libc::prctl(libc::PR_SET_DUMPABLE, 0);
}