Skip to main content

strat9_kernel/syscall/
robust_list.rs

1//! Robust list syscalls: set_robust_list, get_robust_list
2//!
3//! Robust lists allow the kernel to clean up held futex-based mutexes when a
4//! thread dies, preventing deadlocks.
5//!
6//! Linux ABI (robust_list_head in userspace):
7//!   offset 0: struct robust_list list       (next pointer, 8 bytes)
8//!   offset 8: long futex_offset              (offset from list entry to futex int)
9//!   offset 16: struct robust_list *list_op_pending  (pending operation)
10//!
11//! Total: 24 bytes on x86_64.
12
13use crate::{
14    memory::userslice::{UserSliceRead, UserSliceWrite},
15    process::current_task_clone,
16    syscall::error::SyscallError,
17};
18
19/// Maximum number of entries to walk in a robust list (Linux uses 4096).
20const ROBUST_LIST_LIMIT: u32 = 4096;
21
22/// Futex value bit: the owner has died.
23const FUTEX_OWNER_DIED: u32 = 0x4000_0000;
24
25/// Mask for the TID portion of a futex value.
26const TID_MASK: u32 = 0x3FFF_FFFF;
27
28/// SYS_SET_ROBUST_LIST (610): Register the robust list head for the current task.
29///
30/// # Arguments
31/// * `head` - Pointer to a userspace robust_list_head structure
32/// * `len`  - Size of the robust_list_head structure (must be >= 24 on x86_64)
33///
34/// # Returns
35/// * 0 on success
36/// * -EINVAL if len is invalid
37pub fn sys_set_robust_list(head: u64, len: usize) -> Result<u64, SyscallError> {
38    // On x86_64, robust_list_head is 24 bytes. Accept any len >= 24.
39    if head != 0 && len < 24 {
40        return Err(SyscallError::InvalidArgument);
41    }
42
43    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
44    task.robust_list_head
45        .store(head, core::sync::atomic::Ordering::Relaxed);
46    task.robust_list_len
47        .store(len, core::sync::atomic::Ordering::Relaxed);
48    Ok(0)
49}
50
51/// SYS_GET_ROBUST_LIST (611): Get the robust list head for a task.
52///
53/// # Arguments
54/// * `pid`       - PID of the task to query (0 = current task)
55/// * `head_ptr`  - Output pointer for the robust_list_head pointer
56/// * `len_ptr`   - Output pointer for the size
57///
58/// # Returns
59/// * 0 on success
60/// * -EINVAL if pid is invalid
61/// * -EFAULT if head_ptr or len_ptr are invalid
62/// * -EPERM if not permitted to access the task
63pub fn sys_get_robust_list(pid: i64, head_ptr: u64, len_ptr: u64) -> Result<u64, SyscallError> {
64    let task = if pid == 0 {
65        current_task_clone().ok_or(SyscallError::PermissionDenied)?
66    } else {
67        let pid_u = pid as u32;
68        crate::process::get_task_by_pid(pid_u.into()).ok_or(SyscallError::NotFound)?
69    };
70
71    let head = task
72        .robust_list_head
73        .load(core::sync::atomic::Ordering::Relaxed);
74    let len = task
75        .robust_list_len
76        .load(core::sync::atomic::Ordering::Relaxed);
77
78    if head_ptr != 0 {
79        let user = UserSliceWrite::new(head_ptr, core::mem::size_of::<u64>())?;
80        user.copy_from(&head.to_ne_bytes());
81    }
82    if len_ptr != 0 {
83        let user = UserSliceWrite::new(len_ptr, core::mem::size_of::<usize>())?;
84        user.copy_from(&len.to_ne_bytes());
85    }
86
87    Ok(0)
88}
89
90/// Walk the robust list for a dying task and mark owned futexes as "owner died".
91///
92/// Called from `exit_current_task()` before the task's address space is torn down.
93///
94/// # Safety
95/// Reads userspace memory at `robust_list_head`. The address space must still be
96/// valid (not yet freed).
97pub fn cleanup_robust_list(task: &crate::process::Task) {
98    let head_ptr = task
99        .robust_list_head
100        .load(core::sync::atomic::Ordering::Relaxed);
101    if head_ptr == 0 {
102        return;
103    }
104
105    let tid = task.tid;
106
107    // Read the robust_list_head fields from userspace via read_u64:
108    //   offset 0: list.next (first entry pointer, u64)
109    //   offset 8: futex_offset (i64, read as u64 then cast)
110    //   offset 16: list_op_pending (u64)
111    let head_slice = match UserSliceRead::new(head_ptr, 24) {
112        Ok(s) => s,
113        Err(_) => return,
114    };
115
116    let first_entry = match head_slice.read_u64(0) {
117        Ok(v) => v,
118        Err(_) => return,
119    };
120    let futex_offset = match head_slice.read_u64(8) {
121        Ok(v) => v as i64,
122        Err(_) => return,
123    };
124    let pending = match head_slice.read_u64(16) {
125        Ok(v) => v,
126        Err(_) => return,
127    };
128
129    // Handle the pending entry first (it may not be in the list yet)
130    if pending != 0 && pending != head_ptr {
131        mark_futex_owner_died(pending, futex_offset, tid);
132    }
133
134    // Walk the linked list
135    let mut entry = first_entry;
136    let mut count = 0u32;
137
138    while entry != head_ptr && count < ROBUST_LIST_LIMIT {
139        // Read the next pointer from this entry (offset 0 = list.next)
140        let next_ptr = match UserSliceRead::new(entry, 8) {
141            Ok(s) => match s.read_u64(0) {
142                Ok(v) => v,
143                Err(_) => break,
144            },
145            Err(_) => break,
146        };
147
148        // Mark the futex as owner-died if this task still holds it
149        mark_futex_owner_died(entry, futex_offset, tid);
150
151        entry = next_ptr;
152        count += 1;
153    }
154}
155
156/// Mark a futex as owner-died if the current thread still holds it.
157///
158/// Reads the futex value at (entry + futex_offset). If the lower 30 bits
159/// match `tid`, sets bit 30 (FUTEX_OWNER_DIED) and does a FUTEX_WAKE.
160fn mark_futex_owner_died(entry: u64, futex_offset: i64, tid: u32) {
161    let futex_addr = (entry as i64 + futex_offset) as u64;
162
163    // Read the futex word (u32) by reading 4 bytes
164    let futex_val = match read_u32_from_user(futex_addr) {
165        Some(v) => v,
166        None => return,
167    };
168
169    // Check if this task still holds the futex (lower 30 bits == tid)
170    if (futex_val & TID_MASK) != tid {
171        return; // Not held by this task
172    }
173
174    // Set the owner-died bit
175    let new_val = futex_val | FUTEX_OWNER_DIED;
176
177    // Write the new value to userspace
178    if let Ok(user) = UserSliceWrite::new(futex_addr, 4) {
179        user.copy_from(&new_val.to_ne_bytes());
180    }
181
182    // Wake all waiters on this futex
183    let _ = crate::syscall::futex::sys_futex_wake(futex_addr, u32::MAX);
184}
185
186/// Read a u32 from userspace memory at the given address.
187fn read_u32_from_user(addr: u64) -> Option<u32> {
188    let slice = UserSliceRead::new(addr, 4).ok()?;
189    let b0 = slice.read_u8(0).ok()? as u32;
190    let b1 = slice.read_u8(1).ok()? as u32;
191    let b2 = slice.read_u8(2).ok()? as u32;
192    let b3 = slice.read_u8(3).ok()? as u32;
193    Some(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24))
194}