about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-10-21 14:25:26 +0000
committerbors <bors@rust-lang.org>2024-10-21 14:25:26 +0000
commit695a1b67aaafc2ff80d1cf9c50222cb0824660a5 (patch)
treef7182b332579f02f3713b5e9ad15091297bcb03f
parent13e5e4b7594ed2ec76dd6d8ca9d24e029d5bdc3c (diff)
parentc2f43ba09cdc8b79d540373c3c5847480affd5ee (diff)
downloadrust-695a1b67aaafc2ff80d1cf9c50222cb0824660a5.tar.gz
rust-695a1b67aaafc2ff80d1cf9c50222cb0824660a5.zip
Auto merge of #3899 - YohDeadfall:prctl-thread-name, r=RalfJung
Android: Added support for prctl handling thread names

Addresses the first part of #3618.
-rwxr-xr-xsrc/tools/miri/ci/ci.sh2
-rw-r--r--src/tools/miri/src/shims/unix/android/foreign_items.rs4
-rw-r--r--src/tools/miri/src/shims/unix/android/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/android/thread.rs57
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/foreign_items.rs1
-rw-r--r--src/tools/miri/src/shims/unix/linux/foreign_items.rs1
-rw-r--r--src/tools/miri/src/shims/unix/macos/foreign_items.rs1
-rw-r--r--src/tools/miri/src/shims/unix/solarish/foreign_items.rs1
-rw-r--r--src/tools/miri/src/shims/unix/thread.rs12
-rw-r--r--src/tools/miri/test_dependencies/Cargo.lock6
-rw-r--r--src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs10
-rw-r--r--src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr21
-rw-r--r--src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs74
-rw-r--r--src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs1
14 files changed, 185 insertions, 7 deletions
diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh
index ad1b2f4d0c3..4e7cbc50ca0 100755
--- a/src/tools/miri/ci/ci.sh
+++ b/src/tools/miri/ci/ci.sh
@@ -154,7 +154,7 @@ case $HOST_TARGET in
     TEST_TARGET=i686-unknown-freebsd   run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
     TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe
     TEST_TARGET=x86_64-pc-solaris      run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe
-    TEST_TARGET=aarch64-linux-android  run_tests_minimal $BASIC $UNIX time hashmap pthread --skip threadname
+    TEST_TARGET=aarch64-linux-android  run_tests_minimal $BASIC $UNIX time hashmap threadname pthread
     TEST_TARGET=wasm32-wasip2          run_tests_minimal $BASIC wasm
     TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
     TEST_TARGET=thumbv7em-none-eabihf  run_tests_minimal no_std
diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs
index 583a1f65009..b6f04951fc7 100644
--- a/src/tools/miri/src/shims/unix/android/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs
@@ -1,6 +1,7 @@
 use rustc_span::Symbol;
 use rustc_target::spec::abi::Abi;
 
+use crate::shims::unix::android::thread::prctl;
 use crate::*;
 
 pub fn is_dyn_sym(_name: &str) -> bool {
@@ -25,6 +26,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
 
+            // Threading
+            "prctl" => prctl(this, link_name, abi, args, dest)?,
+
             _ => return interp_ok(EmulateItemResult::NotSupported),
         }
         interp_ok(EmulateItemResult::NeedsReturn)
diff --git a/src/tools/miri/src/shims/unix/android/mod.rs b/src/tools/miri/src/shims/unix/android/mod.rs
index 09c6507b24f..1f2a74bac59 100644
--- a/src/tools/miri/src/shims/unix/android/mod.rs
+++ b/src/tools/miri/src/shims/unix/android/mod.rs
@@ -1 +1,2 @@
 pub mod foreign_items;
+pub mod thread;
diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs
new file mode 100644
index 00000000000..6f5f0f74a22
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/android/thread.rs
@@ -0,0 +1,57 @@
+use rustc_span::Symbol;
+use rustc_target::abi::Size;
+use rustc_target::spec::abi::Abi;
+
+use crate::helpers::check_min_arg_count;
+use crate::shims::unix::thread::EvalContextExt as _;
+use crate::*;
+
+const TASK_COMM_LEN: usize = 16;
+
+pub fn prctl<'tcx>(
+    this: &mut MiriInterpCx<'tcx>,
+    link_name: Symbol,
+    abi: Abi,
+    args: &[OpTy<'tcx>],
+    dest: &MPlaceTy<'tcx>,
+) -> InterpResult<'tcx> {
+    // We do not use `check_shim` here because `prctl` is variadic. The argument
+    // count is checked bellow.
+    this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
+
+    // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch.
+    let pr_set_name = 15;
+    let pr_get_name = 16;
+
+    let [op] = check_min_arg_count("prctl", args)?;
+    let res = match this.read_scalar(op)?.to_i32()? {
+        op if op == pr_set_name => {
+            let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?;
+            let name = this.read_scalar(name)?;
+            let thread = this.pthread_self()?;
+            // The Linux kernel silently truncates long names.
+            // https://www.man7.org/linux/man-pages/man2/PR_SET_NAME.2const.html
+            let res =
+                this.pthread_setname_np(thread, name, TASK_COMM_LEN, /* truncate */ true)?;
+            assert!(res);
+            Scalar::from_u32(0)
+        }
+        op if op == pr_get_name => {
+            let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?;
+            let name = this.read_scalar(name)?;
+            let thread = this.pthread_self()?;
+            let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, this);
+            this.check_ptr_access(
+                name.to_pointer(this)?,
+                Size::from_bytes(TASK_COMM_LEN),
+                CheckInAllocMsg::MemoryAccessTest,
+            )?;
+            let res = this.pthread_getname_np(thread, name, len, /* truncate*/ false)?;
+            assert!(res);
+            Scalar::from_u32(0)
+        }
+        op => throw_unsup_format!("Miri does not support `prctl` syscall with op={}", op),
+    };
+    this.write_scalar(res, dest)?;
+    interp_ok(())
+}
diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
index 5204e57705a..71953aca989 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -29,6 +29,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     max_len,
+                    /* truncate */ false,
                 )?;
             }
             "pthread_get_name_np" => {
diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
index e73bde1ddb6..6616a9845c0 100644
--- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
@@ -84,6 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     TASK_COMM_LEN,
+                    /* truncate */ false,
                 )?;
                 let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
                 this.write_scalar(res, dest)?;
diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
index b199992245c..cd07bc9e013 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -181,6 +181,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     thread,
                     this.read_scalar(name)?,
                     this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(),
+                    /* truncate */ false,
                 )? {
                     Scalar::from_u32(0)
                 } else {
diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
index 7f3d0f07bdc..c9c1b01b8b1 100644
--- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
@@ -30,6 +30,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     max_len,
+                    /* truncate */ false,
                 )?;
                 let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
                 this.write_scalar(res, dest)?;
diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs
index 7f97afc8e4b..51256d800a4 100644
--- a/src/tools/miri/src/shims/unix/thread.rs
+++ b/src/tools/miri/src/shims/unix/thread.rs
@@ -64,23 +64,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     }
 
     /// Set the name of the specified thread. If the name including the null terminator
-    /// is longer than `name_max_len`, then `false` is returned.
+    /// is longer or equals to `name_max_len`, then if `truncate` is set the truncated name
+    /// is used as the thread name, otherwise `false` is returned.
     fn pthread_setname_np(
         &mut self,
         thread: Scalar,
         name: Scalar,
         name_max_len: usize,
+        truncate: bool,
     ) -> 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();
+        let mut name = this.read_c_str(name)?.to_owned();
 
         // Comparing with `>=` to account for null terminator.
         if name.len() >= name_max_len {
-            return interp_ok(false);
+            if truncate {
+                name.truncate(name_max_len.saturating_sub(1));
+            } else {
+                return interp_ok(false);
+            }
         }
 
         this.set_thread_name(thread, name);
diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock
index 64bfc84ef20..0a5e9f62dd9 100644
--- a/src/tools/miri/test_dependencies/Cargo.lock
+++ b/src/tools/miri/test_dependencies/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "addr2line"
@@ -128,9 +128,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.158"
+version = "0.2.161"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
 
 [[package]]
 name = "linux-raw-sys"
diff --git a/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs
new file mode 100644
index 00000000000..4b731866aca
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs
@@ -0,0 +1,10 @@
+//! Ensure we report UB when the buffer is smaller than 16 bytes (even if the thread
+//! name would fit in the smaller buffer).
+//@only-target: android  # Miri supports prctl for Android only
+
+fn main() {
+    let mut buf = vec![0u8; 15];
+    unsafe {
+        libc::prctl(libc::PR_GET_NAME, buf.as_mut_ptr().cast::<libc::c_char>()); //~ ERROR: memory access failed: expected a pointer to 16 bytes of memory, but got alloc952 which is only 15 bytes from the end of the allocation
+    }
+}
diff --git a/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr
new file mode 100644
index 00000000000..275a38e593c
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr
@@ -0,0 +1,21 @@
+error: Undefined Behavior: memory access failed: expected a pointer to 16 bytes of memory, but got ALLOC which is only 15 bytes from the end of the allocation
+  --> tests/fail-dep/libc/prctl-threadname.rs:LL:CC
+   |
+LL |         libc::prctl(libc::PR_GET_NAME, buf.as_mut_ptr().cast::<libc::c_char>());
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 16 bytes of memory, but got ALLOC which is only 15 bytes from the end of the allocation
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+help: ALLOC was allocated here:
+  --> tests/fail-dep/libc/prctl-threadname.rs:LL:CC
+   |
+LL |     let mut buf = vec![0u8; 15];
+   |                   ^^^^^^^^^^^^^
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at tests/fail-dep/libc/prctl-threadname.rs:LL:CC
+   = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs b/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs
new file mode 100644
index 00000000000..87ae3753fea
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs
@@ -0,0 +1,74 @@
+//@only-target: android  # Miri supports prctl for Android only
+use std::ffi::{CStr, CString};
+use std::thread;
+
+// The Linux kernel all names 16 bytes long including the null terminator.
+const MAX_THREAD_NAME_LEN: usize = 16;
+
+fn main() {
+    // The short name should be shorter than 16 bytes which POSIX promises
+    // for thread names. The length includes a null terminator.
+    let short_name = "test_named".to_owned();
+    let long_name = std::iter::once("test_named_thread_truncation")
+        .chain(std::iter::repeat(" yada").take(100))
+        .collect::<String>();
+
+    fn set_thread_name(name: &CStr) -> i32 {
+        unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr().cast::<libc::c_char>()) }
+    }
+
+    fn get_thread_name(name: &mut [u8]) -> i32 {
+        assert!(name.len() >= MAX_THREAD_NAME_LEN);
+        unsafe { libc::prctl(libc::PR_GET_NAME, name.as_mut_ptr().cast::<libc::c_char>()) }
+    }
+
+    // Set name via Rust API, get it via prctl.
+    let long_name2 = long_name.clone();
+    thread::Builder::new()
+        .name(long_name.clone())
+        .spawn(move || {
+            let mut buf = vec![0u8; MAX_THREAD_NAME_LEN];
+            assert_eq!(get_thread_name(&mut buf), 0);
+            let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
+            let truncated_name = &long_name2[..long_name2.len().min(MAX_THREAD_NAME_LEN - 1)];
+            assert_eq!(cstr.to_bytes(), truncated_name.as_bytes());
+        })
+        .unwrap()
+        .join()
+        .unwrap();
+
+    // Set name via prctl and get it again (short name).
+    thread::Builder::new()
+        .spawn(move || {
+            // Set short thread name.
+            let cstr = CString::new(short_name.clone()).unwrap();
+            assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); // this should fit
+            assert_eq!(set_thread_name(&cstr), 0);
+
+            let mut buf = vec![0u8; MAX_THREAD_NAME_LEN];
+            assert_eq!(get_thread_name(&mut buf), 0);
+            let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
+            assert_eq!(cstr.to_bytes(), short_name.as_bytes());
+        })
+        .unwrap()
+        .join()
+        .unwrap();
+
+    // Set name via prctl and get it again (long name).
+    thread::Builder::new()
+        .spawn(move || {
+            // Set full thread name.
+            let cstr = CString::new(long_name.clone()).unwrap();
+            assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN);
+            // Names are truncated by the Linux kernel.
+            assert_eq!(set_thread_name(&cstr), 0);
+
+            let mut buf = vec![0u8; MAX_THREAD_NAME_LEN];
+            assert_eq!(get_thread_name(&mut buf), 0);
+            let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
+            assert_eq!(cstr.to_bytes(), &long_name.as_bytes()[..(MAX_THREAD_NAME_LEN - 1)]);
+        })
+        .unwrap()
+        .join()
+        .unwrap();
+}
diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs
index 757d9e20934..0e5b501bbcc 100644
--- a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs
+++ b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs
@@ -1,4 +1,5 @@
 //@ignore-target: windows # No pthreads on Windows
+//@ignore-target: android # No pthread_{get,set}_name on Android
 use std::ffi::{CStr, CString};
 use std::thread;