diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index fcee381ff71..3aeaffe6ad0 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -744,6 +744,44 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     dest,
                 )?;
             }
+            "memcpy" => {
+                let [ptr_dest, ptr_src, n] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr_dest = this.read_pointer(ptr_dest)?;
+                let ptr_src = this.read_pointer(ptr_src)?;
+                let n = this.read_target_usize(n)?;
+                this.mem_copy(
+                    ptr_src,
+                    Align::ONE,
+                    ptr_dest,
+                    Align::ONE,
+                    Size::from_bytes(n),
+                    true,
+                )?;
+                this.write_pointer(ptr_dest, dest)?;
+            }
+            "strcpy" => {
+                let [ptr_dest, ptr_src] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr_dest = this.read_pointer(ptr_dest)?;
+                let ptr_src = this.read_pointer(ptr_src)?;
+
+                // We use `read_c_str` to determine the amount of data to copy,
+                // and then use `mem_copy` for the actual copy. This means
+                // pointer provenance is preserved by this implementation of `strcpy`.
+                // That is properly overly cautious, but there also is no fundamental
+                // reason to have `strcpy` destroy pointer provenance.
+                let n = this.read_c_str(ptr_src)?.len().checked_add(1).unwrap();
+                this.mem_copy(
+                    ptr_src,
+                    Align::ONE,
+                    ptr_dest,
+                    Align::ONE,
+                    Size::from_bytes(n),
+                    true,
+                )?;
+                this.write_pointer(ptr_dest, dest)?;
+            }
 
             // math functions (note that there are also intrinsics for some other functions)
             #[rustfmt::skip]
diff --git a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
index 6fffbc9b4ac..8be9cd983a7 100644
--- a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
+++ b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs
@@ -302,6 +302,83 @@ fn test_posix_mkstemp() {
     }
 }
 
+fn test_memcpy() {
+    unsafe {
+        let src = [1i8, 2, 3];
+        let dest = libc::calloc(3, 1);
+        libc::memcpy(dest, src.as_ptr() as *const libc::c_void, 3);
+        let slc = std::slice::from_raw_parts(dest as *const i8, 3);
+        assert_eq!(*slc, [1i8, 2, 3]);
+        libc::free(dest);
+    }
+
+    unsafe {
+        let src = [1i8, 2, 3];
+        let dest = libc::calloc(4, 1);
+        libc::memcpy(dest, src.as_ptr() as *const libc::c_void, 3);
+        let slc = std::slice::from_raw_parts(dest as *const i8, 4);
+        assert_eq!(*slc, [1i8, 2, 3, 0]);
+        libc::free(dest);
+    }
+
+    unsafe {
+        let src = 123_i32;
+        let mut dest = 0_i32;
+        libc::memcpy(
+            &mut dest as *mut i32 as *mut libc::c_void,
+            &src as *const i32 as *const libc::c_void,
+            std::mem::size_of::<i32>(),
+        );
+        assert_eq!(dest, src);
+    }
+
+    unsafe {
+        let src = Some(123);
+        let mut dest: Option<i32> = None;
+        libc::memcpy(
+            &mut dest as *mut Option<i32> as *mut libc::c_void,
+            &src as *const Option<i32> as *const libc::c_void,
+            std::mem::size_of::<Option<i32>>(),
+        );
+        assert_eq!(dest, src);
+    }
+
+    unsafe {
+        let src = &123;
+        let mut dest = &42;
+        libc::memcpy(
+            &mut dest as *mut &'static i32 as *mut libc::c_void,
+            &src as *const &'static i32 as *const libc::c_void,
+            std::mem::size_of::<&'static i32>(),
+        );
+        assert_eq!(*dest, 123);
+    }
+}
+
+fn test_strcpy() {
+    use std::ffi::{CStr, CString};
+
+    // case: src_size equals dest_size
+    unsafe {
+        let src = CString::new("rust").unwrap();
+        let size = src.as_bytes_with_nul().len();
+        let dest = libc::malloc(size);
+        libc::strcpy(dest as *mut libc::c_char, src.as_ptr());
+        assert_eq!(CStr::from_ptr(dest as *const libc::c_char), src.as_ref());
+        libc::free(dest);
+    }
+
+    // case: src_size is less than dest_size
+    unsafe {
+        let src = CString::new("rust").unwrap();
+        let size = src.as_bytes_with_nul().len();
+        let dest = libc::malloc(size + 1);
+        libc::strcpy(dest as *mut libc::c_char, src.as_ptr());
+        assert_eq!(CStr::from_ptr(dest as *const libc::c_char), src.as_ref());
+        libc::free(dest);
+    }
+}
+
 #[cfg(target_os = "linux")]
 fn test_sigrt() {
     let min = libc::SIGRTMIN();
@@ -333,6 +410,9 @@ fn main() {
     test_isatty();
     test_clocks();
 
+    test_memcpy();
+    test_strcpy();
+
     #[cfg(target_os = "linux")]
     {
         test_posix_fadvise();