Issue #8467: Pure Python implementation of subprocess encodes the error message
using surrogatepass error handler to support surrogates in the message
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index ec391cb..b8bcd5e 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1204,8 +1204,9 @@
                                         errno = 0
                                     message = '%s:%x:%s' % (exc_type.__name__,
                                                             errno, exc_value)
-                                    os.write(errpipe_write, message.encode())
-                                except:
+                                    message = message.encode(errors="surrogatepass")
+                                    os.write(errpipe_write, message)
+                                except Exception:
                                     # We MUST not allow anything odd happening
                                     # above to prevent us from exiting below.
                                     pass
@@ -1255,7 +1256,7 @@
                 for fd in (p2cwrite, c2pread, errread):
                     if fd != -1:
                         os.close(fd)
-                err_msg = err_msg.decode()
+                err_msg = err_msg.decode(errors="surrogatepass")
                 if issubclass(child_exception_type, OSError) and hex_errno:
                     errno = int(hex_errno, 16)
                     if errno != 0:
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index ce0bcfe..d47e01c 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -782,6 +782,25 @@
         self.assertStderrEqual(stderr, b'')
         self.assertEqual(p.wait(), -signal.SIGTERM)
 
+    def test_surrogates(self):
+        def prepare():
+            raise ValueError("surrogate:\uDCff")
+
+        try:
+            subprocess.call(
+                [sys.executable, "-c", "pass"],
+                preexec_fn=prepare)
+        except ValueError as err:
+            # Pure Python implementations keeps the message
+            self.assertIsNone(subprocess._posixsubprocess)
+            self.assertEqual(str(err), "surrogate:\uDCff")
+        except RuntimeError as err:
+            # _posixsubprocess uses a default message
+            self.assertIsNotNone(subprocess._posixsubprocess)
+            self.assertEqual(str(err), "Exception occurred in preexec_fn.")
+        else:
+            self.fail("Expected ValueError or RuntimeError")
+
 
 @unittest.skipUnless(mswindows, "Windows specific tests")
 class Win32ProcessTestCase(BaseTestCase):