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

+5
View File
@@ -206,6 +206,7 @@ fn available_identities(identities: &[Identity]) -> Vec<u32> {
}
fn choose_identity(available: &[u32]) -> Option<u32> {
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
let current = unsafe { libc::geteuid() };
available
.iter()
@@ -230,6 +231,7 @@ fn process_subject() -> Result<(String, HashMap<String, Value<'static>>), String
details.insert("pid".to_owned(), Value::from(std::process::id()));
details.insert(
"uid".to_owned(),
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
Value::from(unsafe { libc::geteuid() } as i32),
);
details.insert("start-time".to_owned(), Value::from(started));
@@ -311,6 +313,7 @@ async fn run_agent() -> Result<(), Box<dyn std::error::Error>> {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// SAFETY: PR_SET_DUMPABLE takes an integer value and does not dereference caller memory.
unsafe {
libc::prctl(libc::PR_SET_DUMPABLE, 0);
}
@@ -551,6 +554,7 @@ mod tests {
assert_eq!(details["pid"], Value::from(std::process::id()));
assert_eq!(
details["uid"],
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
Value::from(unsafe { libc::geteuid() } as i32)
);
assert!(u64::try_from(details["start-time"].clone()).unwrap() > 0);
@@ -558,6 +562,7 @@ mod tests {
#[test]
fn chooses_current_identity_when_available() {
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
let current = unsafe { libc::geteuid() };
let identity = |uid: u32| {
(
+63
View File
@@ -31,6 +31,8 @@ struct Api {
impl Api {
fn load() -> Result<Self, String> {
// SAFETY: The typed symbols match the system polkit ABI, and the static library handles
// keep them loaded for every session.
unsafe {
// GLib keeps registered types and their function pointers for the
// lifetime of the process, including between authentication sessions.
@@ -142,46 +144,77 @@ fn prompt_process(request: AuthenticationRequest) -> Result<PromptProcess, Strin
Ok(process)
}
/// # Safety
/// `data` must be the live Sender retained by SessionGuard, and `prompt` must
/// be a NUL-terminated string from the polkit request signal.
unsafe extern "C" fn request_signal(
_session: Object,
prompt: *const c_char,
echo: c_int,
data: *mut c_void,
) {
// SAFETY: SessionGuard retains the boxed Sender until the session is cancelled and
// unreferenced, after its synchronous signal callbacks finish.
let tx = unsafe { &*(data as *const Sender<SessionEvent>) };
// SAFETY: The polkit request signal provides a live NUL-terminated prompt for the callback
// duration.
let prompt = unsafe { CStr::from_ptr(prompt) }
.to_string_lossy()
.into_owned();
let _ = tx.send(SessionEvent::Request(prompt, echo != 0));
}
/// # Safety
/// `data` must be the live Sender retained by SessionGuard, and `text` must
/// be a NUL-terminated string from the polkit show-info signal.
unsafe extern "C" fn info(_session: Object, text: *const c_char, data: *mut c_void) {
// SAFETY: SessionGuard retains the boxed Sender until the session is cancelled and
// unreferenced, after its synchronous signal callbacks finish.
let tx = unsafe { &*(data as *const Sender<SessionEvent>) };
// SAFETY: The polkit text signal provides a live NUL-terminated string for the callback
// duration.
let text = unsafe { CStr::from_ptr(text) }
.to_string_lossy()
.into_owned();
let _ = tx.send(SessionEvent::Info(text));
}
/// # Safety
/// `data` must be the live Sender retained by SessionGuard, and `text` must
/// be a NUL-terminated string from the polkit show-error signal.
unsafe extern "C" fn error(_session: Object, text: *const c_char, data: *mut c_void) {
// SAFETY: SessionGuard retains the boxed Sender until the session is cancelled and
// unreferenced, after its synchronous signal callbacks finish.
let tx = unsafe { &*(data as *const Sender<SessionEvent>) };
// SAFETY: The polkit text signal provides a live NUL-terminated string for the callback
// duration.
let text = unsafe { CStr::from_ptr(text) }
.to_string_lossy()
.into_owned();
let _ = tx.send(SessionEvent::Error(text));
}
/// # Safety
/// `data` must be the live Sender retained by SessionGuard until this
/// synchronous polkit completed callback returns.
unsafe extern "C" fn completed(_session: Object, success: c_int, data: *mut c_void) {
// SAFETY: SessionGuard retains the boxed Sender until the session is cancelled and
// unreferenced, after its synchronous signal callbacks finish.
let tx = unsafe { &*(data as *const Sender<SessionEvent>) };
let _ = tx.send(SessionEvent::Completed(success != 0));
}
/// # Safety
/// `session` must be a live GObject. The callback must match the named
/// signal ABI, and `data` must remain valid until the session is destroyed.
unsafe fn connect(
session: Object,
name: &'static CStr,
callback: gobject_ffi::GCallback,
data: *mut c_void,
) {
// SAFETY: The caller supplies a live session, a static NUL-terminated signal name and a
// callback matching that signal; its data remains alive until disconnection.
unsafe {
gobject_ffi::g_signal_connect_data(
session.cast(),
@@ -210,6 +243,8 @@ struct SessionGuard {
impl Drop for SessionGuard {
fn drop(&mut self) {
// SAFETY: This guard owns the session reference and boxed Sender. Cancellation and final
// unref finish callbacks before the Sender is reclaimed exactly once.
unsafe {
if !self.completed {
(self.cancel)(self.object);
@@ -264,11 +299,16 @@ fn run_session(
},
)?;
}
// SAFETY: user_new is a typed symbol retained by Api and accepts the selected integer UID.
let identity = unsafe { (api.user_new)(request.selected_uid as c_int) };
if identity.is_null() {
return Err("Could not create polkit identity".into());
}
// SAFETY: identity is a non-null live polkit object and cookie is a NUL-terminated CString
// retained for this call.
let session = unsafe { (api.session_new)(identity, cookie.as_ptr()) };
// SAFETY: The caller owns the identity reference returned by user_new; session_new has
// retained what it needs.
unsafe { gobject_ffi::g_object_unref(identity.cast()) };
if session.is_null() {
return Err("Could not create polkit session".into());
@@ -283,6 +323,9 @@ fn run_session(
completed: false,
};
let signal_data = signal_data.cast();
// SAFETY: The session and boxed signal data outlive all callbacks. Each callback signature
// matches its named GLib signal before erasure to GCallback; initiate uses that same live
// session.
unsafe {
connect(
session,
@@ -378,6 +421,8 @@ fn run_session(
&& let Some(response) = response_buffer(&value)
{
value.zeroize();
// SAFETY: SessionGuard keeps the session alive and response_buffer
// supplies a NUL-terminated response for the synchronous call.
unsafe { (api.response)(session, response.as_ptr().cast()) };
}
value.zeroize();
@@ -408,6 +453,8 @@ fn run_session(
.map_err(|e| e.to_string())?
.is_some())
{
// SAFETY: SessionGuard still owns this live session and the cancellation symbol
// remains loaded.
unsafe { (api.cancel)(session) };
stopped = true;
}
@@ -495,7 +542,11 @@ mod tests {
static RESPONSES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
/// # Safety
/// `session` must be the live test polkit session with its registered signals.
unsafe extern "C" fn fake_initiate(session: Object) {
// SAFETY: The fake receives a live test session and emits request with the registered
// string and integer argument types.
unsafe {
gobject_ffi::g_signal_emit_by_name(
session.cast(),
@@ -506,14 +557,22 @@ mod tests {
}
}
/// # Safety
/// `session` must be the live test polkit session with its registered signals.
unsafe extern "C" fn fake_cancel(session: Object) {
// SAFETY: The fake receives a live test session and emits completed with its registered
// gboolean argument type.
unsafe {
gobject_ffi::g_signal_emit_by_name(session.cast(), c"completed".as_ptr(), 0 as c_int);
}
}
/// # Safety
/// `session` must be the live test polkit session with its registered signals.
unsafe extern "C" fn fake_response(session: Object, _value: *const c_char) {
RESPONSES.fetch_add(1, Ordering::SeqCst);
// SAFETY: The fake receives a live test session and emits completed with its registered
// gboolean argument type.
unsafe {
gobject_ffi::g_signal_emit_by_name(session.cast(), c"completed".as_ptr(), 1 as c_int);
}
@@ -605,7 +664,11 @@ mod tests {
.unwrap();
}
/// # Safety
/// `session` must be the live test polkit session with its registered signals.
unsafe extern "C" fn fake_failed_response(session: Object, _value: *const c_char) {
// SAFETY: The fake receives a live test session and emits completed with its registered
// gboolean argument type.
unsafe {
gobject_ffi::g_signal_emit_by_name(session.cast(), c"completed".as_ptr(), 0 as c_int);
}