diff --git a/include/string.h b/include/string.h
index 24a61bcff7..3c784be997 100644
--- a/include/string.h
+++ b/include/string.h
@@ -49,6 +49,7 @@ extern void __bzero (void *__s, size_t __n) __THROW __nonnull ((1));
 extern int __ffs (int __i) __attribute__ ((const));
 
 extern char *__strerror_r (int __errnum, char *__buf, size_t __buflen);
+extern char *__strerror_lookup (int errnum) attribute_hidden;
 
 /* Called as part of the thread shutdown sequence.  */
 void __strsignal_thread_freeres (void) attribute_hidden;
diff --git a/manual/errno.texi b/manual/errno.texi
index 8cb4ce8b48..dbb0c4a66d 100644
--- a/manual/errno.texi
+++ b/manual/errno.texi
@@ -1147,7 +1147,7 @@ name of the program that encountered the error.
 
 @deftypefun {char *} strerror (int @var{errnum})
 @standards{ISO, string.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:strerror}}@asunsafe{@ascuheap{} @ascuintl{}}@acunsafe{@acsmem{}}}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c Calls strerror_r with a static buffer allocated with malloc on the
 @c first use.
 The @code{strerror} function maps the error code (@pxref{Checking for
diff --git a/string/_strerror.c b/string/_strerror.c
index 985fd4e3c6..f6a0b89b38 100644
--- a/string/_strerror.c
+++ b/string/_strerror.c
@@ -15,60 +15,64 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <libintl.h>
-#include <stdbool.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
+#include <stdlib.h>
+#include <array_length.h>
 #include <sys/param.h>
-#include <_itoa.h>
+#include <tls_internal-struct.h>
+
+#define UNK_ERR_STR "Unknown error "
+_Static_assert (sizeof (UNK_ERR_STR) <= strerror_str_len,
+		UNK_ERR_STR " > strerror_str_len");
 
-/* It is critical here that we always use the `dcgettext' function for
-   the message translation.  Since <libintl.h> only defines the macro
-   `dgettext' to use `dcgettext' for optimizing programs this is not
-   always guaranteed.  */
-#ifndef dgettext
-# include <locale.h>		/* We need LC_MESSAGES.  */
-# define dgettext(domainname, msgid) dcgettext (domainname, msgid, LC_MESSAGES)
-#endif
+char *
+__strerror_lookup (int errnum)
+{
+  if (__glibc_unlikely (errnum < 0 || errnum >= _sys_nerr_internal))
+    return NULL;
+  return (char *) _sys_errlist_internal[errnum];
+}
 
 /* Return a string describing the errno code in ERRNUM.  */
 char *
 __strerror_r (int errnum, char *buf, size_t buflen)
 {
-  if (__glibc_unlikely (errnum < 0 || errnum >= _sys_nerr_internal
-			|| _sys_errlist_internal[errnum] == NULL))
-    {
-      /* Buffer we use to print the number in.  For a maximum size for
-	 `int' of 8 bytes we never need more than 20 digits.  */
-      char numbuf[21];
-      const char *unk = _("Unknown error ");
-      size_t unklen = strlen (unk);
-      char *p, *q;
-      bool negative = errnum < 0;
+  char *r = __strerror_lookup (errnum);
+  if (r != NULL)
+    return r;
 
-      numbuf[20] = '\0';
-      p = _itoa_word (abs (errnum), &numbuf[20], 10, 0);
+  const size_t unklen = array_length (UNK_ERR_STR) - 1;
+  char *q = __mempcpy (buf, UNK_ERR_STR, MIN (buflen, unklen));
+  if (unklen < buflen)
+    {
+      char numstr[INT_STRLEN_BOUND (int) + 1];
+      size_t numstrlen = 1;
 
-      /* Now construct the result while taking care for the destination
-	 buffer size.  */
-      q = __mempcpy (buf, unk, MIN (unklen, buflen));
-      if (negative && unklen < buflen)
+      char *p = numstr;
+      long int errn = errnum;
+      if (errnum < 0)
 	{
-	  *q++ = '-';
-	  ++unklen;
+	  *p++ = '-';
+	  errn = labs (errnum);
+	  numstrlen++;
 	}
-      if (unklen < buflen)
-	memcpy (q, p, MIN ((size_t) (&numbuf[21] - p), buflen - unklen));
+      for (long int d = errn; p++, (d /= 10) != 0; )
+	continue;
+      *p = '\0';
+      for (long int d = errn;
+	    *--p = '0' + d % 10, (d /= 10) != 0;
+	   numstrlen++)
+	continue;
 
-      /* Terminate the string in any case.  */
-      if (buflen > 0)
-	buf[buflen - 1] = '\0';
-
-      return buf;
+      memcpy (q, numstr, MIN (MIN (numstrlen, INT_STRLEN_BOUND (int)) + 1,
+			      buflen - unklen));
     }
 
-  return (char *) _(_sys_errlist_internal[errnum]);
+  if (buflen > 0)
+    buf[buflen - 1] = '\0';
+
+  return buf;
 }
 weak_alias (__strerror_r, strerror_r)
 libc_hidden_def (__strerror_r)
diff --git a/string/strerror.c b/string/strerror.c
index 283ab70f91..4c6f88c682 100644
--- a/string/strerror.c
+++ b/string/strerror.c
@@ -15,29 +15,17 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <libintl.h>
-#include <stdio.h>
 #include <string.h>
-#include <errno.h>
-
-/* Return a string describing the errno code in ERRNUM.
-   The storage is good only until the next call to strerror.
-   Writing to the storage causes undefined behavior.  */
-libc_freeres_ptr (static char *buf);
+#include <tls_internal.h>
 
 char *
 strerror (int errnum)
 {
-  char *ret = __strerror_r (errnum, NULL, 0);
-  int saved_errno;
-
-  if (__glibc_likely (ret != NULL))
+  char *ret = __strerror_lookup (errnum);
+  if (ret != NULL)
     return ret;
-  saved_errno = errno;
-  if (buf == NULL)
-    buf = malloc (1024);
-  __set_errno (saved_errno);
-  if (buf == NULL)
-    return _("Unknown error");
-  return __strerror_r (errnum, buf, 1024);
+
+  char *buffer = __glibc_tls_internal ()->strerror_buf;
+  __strerror_r (errnum, buffer, TLS_INTERNAL_STRERROR_LEN);
+  return buffer;
 }
diff --git a/sysdeps/generic/tls_internal-struct.h b/sysdeps/generic/tls_internal-struct.h
index ca235a93db..4416228757 100644
--- a/sysdeps/generic/tls_internal-struct.h
+++ b/sysdeps/generic/tls_internal-struct.h
@@ -22,12 +22,17 @@
 #include <intprops.h>
 
 enum { strsignal_str_len = 20 };
+enum { strerror_str_len = 37 };
 
 struct tls_internal_t
 {
   /* Used on strsignal.c.  */
   char strsignal_buf[strsignal_str_len + INT_STRLEN_BOUND (int) + 1];
   char *strsignal_l_buf;
+  char strerror_buf[strerror_str_len + INT_STRLEN_BOUND (int) + 1];
 };
 
+#define TLS_INTERNAL_STRERROR_LEN \
+  sizeof ((struct tls_internal_t){0}.strerror_buf)
+
 #endif
diff --git a/sysdeps/mach/_strerror.c b/sysdeps/mach/_strerror.c
index d932b1bd58..f3969b576f 100644
--- a/sysdeps/mach/_strerror.c
+++ b/sysdeps/mach/_strerror.c
@@ -15,22 +15,45 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <libintl.h>
 #include <stdio.h>
 #include <string.h>
 #include <mach/error.h>
 #include <errorlib.h>
 #include <sys/param.h>
 #include <_itoa.h>
+#include <tls_internal-struct.h>
 
-/* It is critical here that we always use the `dcgettext' function for
-   the message translation.  Since <libintl.h> only defines the macro
-   `dgettext' to use `dcgettext' for optimizing programs this is not
-   always guaranteed.  */
-#ifndef dgettext
-# include <locale.h>		/* We need LC_MESSAGES.  */
-# define dgettext(domainname, msgid) dcgettext (domainname, msgid, LC_MESSAGES)
-#endif
+#define UNK_SYSTEM_ERR_STR "Error in unknown error system: "
+_Static_assert (sizeof (UNK_SYSTEM_ERR_STR) <= strerror_str_len,
+		UNK_SYSTEM_ERR_STR " > strerror_str_len");
+#define UNK_SUBSYSTEM_ERR_STR "Unknown error "
+_Static_assert (sizeof (UNK_SUBSYSTEM_ERR_STR) <= strerror_str_len,
+		UNK_SUBSYSTEM_ERR_STR " > strerror_str_len");
+
+extern void __mach_error_map_compat (int *);
+
+char *
+__strerror_lookup (int errnum)
+{
+  __mach_error_map_compat (&errnum);
+
+  int system = err_get_system (errnum);
+  int sub = err_get_sub (errnum);
+  int code = err_get_code (errnum);
+
+  if (system > err_max_system || ! __mach_error_systems[system].bad_sub)
+    return NULL;
+
+  const struct error_system *es = &__mach_error_systems[system];
+
+  if (sub >= es->max_sub)
+    return (char *) es->bad_sub;
+
+  if (code >= es->subsystem[sub].max_code)
+    return NULL;
+
+  return (char *) es->subsystem[sub].codes[code];
+}
 
 /* Return a string describing the errno code in ERRNUM.  */
 char *
@@ -40,7 +63,6 @@ __strerror_r (int errnum, char *buf, size_t buflen)
   int sub;
   int code;
   const struct error_system *es;
-  extern void __mach_error_map_compat (int *);
 
   __mach_error_map_compat (&errnum);
 
@@ -53,7 +75,7 @@ __strerror_r (int errnum, char *buf, size_t buflen)
       /* Buffer we use to print the number in.  For a maximum size for
 	 `int' of 8 bytes we never need more than 20 digits.  */
       char numbuf[21];
-      const char *unk = _("Error in unknown error system: ");
+      const char *unk = UNK_SYSTEM_ERR_STR;
       const size_t unklen = strlen (unk);
       char *p, *q;
 
@@ -83,7 +105,7 @@ __strerror_r (int errnum, char *buf, size_t buflen)
       /* Buffer we use to print the number in.  For a maximum size for
 	 `int' of 8 bytes we never need more than 20 digits.  */
       char numbuf[21];
-      const char *unk = _("Unknown error ");
+      const char *unk = UNK_SUBSYSTEM_ERR_STR;
       const size_t unklen = strlen (unk);
       char *p, *q;
       size_t len = strlen (es->subsystem[sub].subsys_name);
@@ -114,7 +136,7 @@ __strerror_r (int errnum, char *buf, size_t buflen)
       return buf;
     }
 
-  return (char *) _(es->subsystem[sub].codes[code]);
+  return (char *) es->subsystem[sub].codes[code];
 }
 libc_hidden_def (__strerror_r)
 weak_alias (__strerror_r, strerror_r)
