Fixed pthread get/set name for macOS

This commit is contained in:
Yoh Deadfall 2024-10-09 22:28:28 +03:00
parent 5cdee0781f
commit 56c0612003
6 changed files with 91 additions and 23 deletions

View File

@ -39,6 +39,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.read_scalar(thread)?,
this.read_scalar(name)?,
this.read_scalar(len)?,
false,
)?;
}

View File

@ -84,6 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.read_scalar(name)?,
TASK_COMM_LEN,
)?;
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
this.write_scalar(res, dest)?;
}
"pthread_getname_np" => {
@ -93,14 +94,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// In case of glibc, the length of the output buffer must
// be not shorter than TASK_COMM_LEN.
let len = this.read_scalar(len)?;
let res = if len.to_target_usize(this)? < TASK_COMM_LEN as u64 {
this.eval_libc("ERANGE")
} else {
this.pthread_getname_np(
let res = if len.to_target_usize(this)? >= TASK_COMM_LEN as u64
&& this.pthread_getname_np(
this.read_scalar(thread)?,
this.read_scalar(name)?,
len,
)?
/* truncate*/ false,
)? {
Scalar::from_u32(0)
} else {
this.eval_libc("ERANGE")
};
this.write_scalar(res, dest)?;
}

View File

@ -164,13 +164,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Threading
"pthread_setname_np" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// The real implementation has logic in two places:
// * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200,
// * in kernel at https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/proc_info.c#L3218-L3227.
//
// The function in libc calls the kernel to validate
// the security policies and the input. If all of the requirements
// are met, then the name is set and 0 is returned. Otherwise, if
// the specified name is lomnger than MAXTHREADNAMESIZE, then
// ENAMETOOLONG is returned.
//
// FIXME: the real implementation maybe returns ESRCH if the thread ID is invalid.
let thread = this.pthread_self()?;
let max_len = this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?;
let res = this.pthread_setname_np(
let res = if this.pthread_setname_np(
thread,
this.read_scalar(name)?,
max_len.try_into().unwrap(),
)?;
this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(),
)? {
Scalar::from_u32(0)
} else {
this.eval_libc("ENAMETOOLONG")
};
// Contrary to the manpage, `pthread_setname_np` on macOS still
// returns an integer indicating success.
this.write_scalar(res, dest)?;
@ -178,10 +193,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"pthread_getname_np" => {
let [thread, name, len] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let res = this.pthread_getname_np(
// The function's behavior isn't portable between platforms.
// In case of macOS, a truncated name (due to a too small buffer)
// does not lead to an error.
//
// For details, see the implementation at
// https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1160-L1175.
// The key part is the strlcpy, which truncates the resulting value,
// but always null terminates (except for zero sized buffers).
//
// FIXME: the real implementation returns ESRCH if the thread ID is invalid.
let res = Scalar::from_u32(0);
this.pthread_getname_np(
this.read_scalar(thread)?,
this.read_scalar(name)?,
this.read_scalar(len)?,
/* truncate */ true,
)?;
this.write_scalar(res, dest)?;
}

View File

@ -31,16 +31,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.read_scalar(name)?,
max_len,
)?;
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
this.write_scalar(res, dest)?;
}
"pthread_getname_np" => {
let [thread, name, len] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// https://github.com/illumos/illumos-gate/blob/c56822be04b6c157c8b6f2281e47214c3b86f657/usr/src/lib/libc/port/threads/thr.c#L2449-L2480
let res = this.pthread_getname_np(
this.read_scalar(thread)?,
this.read_scalar(name)?,
this.read_scalar(len)?,
/* truncate */ false,
)?;
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
this.write_scalar(res, dest)?;
}

View File

@ -63,38 +63,41 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_uint(thread_id.to_u32(), this.libc_ty_layout("pthread_t").size))
}
/// Set the name of the current thread. `max_name_len` is the maximal length of the name
/// including the null terminator.
/// Set the name of the specified thread. If the name including the null terminator
/// is longer than `name_max_len`, then `false` is returned.
fn pthread_setname_np(
&mut self,
thread: Scalar,
name: Scalar,
max_name_len: usize,
) -> InterpResult<'tcx, Scalar> {
name_max_len: usize,
) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
let thread = ThreadId::try_from(thread).unwrap();
let name = name.to_pointer(this)?;
let name = this.read_c_str(name)?.to_owned();
// Comparing with `>=` to account for null terminator.
if name.len() >= max_name_len {
return interp_ok(this.eval_libc("ERANGE"));
if name.len() >= name_max_len {
return interp_ok(false);
}
this.set_thread_name(thread, name);
interp_ok(Scalar::from_u32(0))
interp_ok(true)
}
/// Get the name of the specified thread. If the thread name doesn't fit
/// the buffer, then if `truncate` is set the truncated name is written out,
/// otherwise `false` is returned.
fn pthread_getname_np(
&mut self,
thread: Scalar,
name_out: Scalar,
len: Scalar,
) -> InterpResult<'tcx, Scalar> {
truncate: bool,
) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
@ -104,9 +107,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// FIXME: we should use the program name if the thread name is not set
let name = this.get_thread_name(thread).unwrap_or(b"<unnamed>").to_owned();
let (success, _written) = this.write_c_str(&name, name_out, len)?;
let name = match truncate {
true => &name[..name.len().min(len.try_into().unwrap_or(usize::MAX).saturating_sub(1))],
false => &name,
};
interp_ok(if success { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") })
let (success, _written) = this.write_c_str(name, name_out, len)?;
interp_ok(success)
}
fn sched_yield(&mut self) -> InterpResult<'tcx, ()> {

View File

@ -74,6 +74,26 @@ fn main() {
// large enough for the thread name.
#[cfg(target_os = "linux")]
assert_eq!(get_thread_name(&mut buf[..15]), libc::ERANGE);
// Solaris compatible implementations return an error,
// if the buffer is shorter than the thread name.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
assert_eq!(get_thread_name(&mut buf[..4]), libc::ERANGE);
// For libc implementation for macOS it's not an error
// for a buffer being too short for the thread name.
#[cfg(target_os = "macos")]
{
// Ensure that a zero sized buffer returns no error.
assert_eq!(get_thread_name(&mut buf[..0]), 0);
// Ensure that a shorter tnan required buffer still returns no error,
// and gives a prefix of the thread name.
assert_eq!(get_thread_name(&mut buf[..4]), 0);
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
assert_eq!(cstr.to_bytes_with_nul().len(), 4);
assert!(short_name.as_bytes().starts_with(cstr.to_bytes()));
}
})
.unwrap()
.join()
@ -105,8 +125,12 @@ fn main() {
// But with a too long name it should fail (except on FreeBSD where the
// function has no return, hence cannot indicate failure).
#[cfg(not(target_os = "freebsd"))]
assert_ne!(set_thread_name(&CString::new(long_name).unwrap()), 0);
// On macOS, the error code is different.
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
assert_eq!(set_thread_name(&CString::new(long_name).unwrap()), libc::ERANGE);
#[cfg(target_os = "macos")]
assert_eq!(set_thread_name(&CString::new(long_name).unwrap()), libc::ENAMETOOLONG);
})
.unwrap()
.join()