Use a placeholder for unknown errors in ERR_*_error_string.

Change-Id: I3a16fa731cfa7c92e5fec19f78ae48650921f626
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/47104
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/err/err.c b/crypto/err/err.c
index 7973a0e..4aab75b 100644
--- a/crypto/err/err.c
+++ b/crypto/err/err.c
@@ -368,84 +368,6 @@
   errno = 0;
 }
 
-char *ERR_error_string(uint32_t packed_error, char *ret) {
-  static char buf[ERR_ERROR_STRING_BUF_LEN];
-
-  if (ret == NULL) {
-    // TODO(fork): remove this.
-    ret = buf;
-  }
-
-#if !defined(NDEBUG)
-  // This is aimed to help catch callers who don't provide
-  // |ERR_ERROR_STRING_BUF_LEN| bytes of space.
-  OPENSSL_memset(ret, 0, ERR_ERROR_STRING_BUF_LEN);
-#endif
-
-  return ERR_error_string_n(packed_error, ret, ERR_ERROR_STRING_BUF_LEN);
-}
-
-char *ERR_error_string_n(uint32_t packed_error, char *buf, size_t len) {
-  char lib_buf[64], reason_buf[64];
-  const char *lib_str, *reason_str;
-  unsigned lib, reason;
-
-  if (len == 0) {
-    return NULL;
-  }
-
-  lib = ERR_GET_LIB(packed_error);
-  reason = ERR_GET_REASON(packed_error);
-
-  lib_str = ERR_lib_error_string(packed_error);
-  reason_str = ERR_reason_error_string(packed_error);
-
-  if (lib_str == NULL) {
-    BIO_snprintf(lib_buf, sizeof(lib_buf), "lib(%u)", lib);
-    lib_str = lib_buf;
-  }
-
- if (reason_str == NULL) {
-    BIO_snprintf(reason_buf, sizeof(reason_buf), "reason(%u)", reason);
-    reason_str = reason_buf;
-  }
-
-  BIO_snprintf(buf, len, "error:%08" PRIx32 ":%s:OPENSSL_internal:%s",
-               packed_error, lib_str, reason_str);
-
-  if (strlen(buf) == len - 1) {
-    // output may be truncated; make sure we always have 5 colon-separated
-    // fields, i.e. 4 colons.
-    static const unsigned num_colons = 4;
-    unsigned i;
-    char *s = buf;
-
-    if (len <= num_colons) {
-      // In this situation it's not possible to ensure that the correct number
-      // of colons are included in the output.
-      return buf;
-    }
-
-    for (i = 0; i < num_colons; i++) {
-      char *colon = strchr(s, ':');
-      char *last_pos = &buf[len - 1] - num_colons + i;
-
-      if (colon == NULL || colon > last_pos) {
-        // set colon |i| at last possible position (buf[len-1] is the
-        // terminating 0). If we're setting this colon, then all whole of the
-        // rest of the string must be colons in order to have the correct
-        // number.
-        OPENSSL_memset(last_pos, ':', num_colons - i);
-        break;
-      }
-
-      s = colon + 1;
-    }
-  }
-
-  return buf;
-}
-
 // err_string_cmp is a compare function for searching error values with
 // |bsearch| in |err_string_lookup|.
 static int err_string_cmp(const void *a, const void *b) {
@@ -530,7 +452,7 @@
     "User defined functions",       // ERR_LIB_USER
 };
 
-const char *ERR_lib_error_string(uint32_t packed_error) {
+static const char *err_lib_error_string(uint32_t packed_error) {
   const uint32_t lib = ERR_GET_LIB(packed_error);
 
   if (lib >= ERR_NUM_LIBS) {
@@ -539,11 +461,16 @@
   return kLibraryNames[lib];
 }
 
+const char *ERR_lib_error_string(uint32_t packed_error) {
+  const char *ret = err_lib_error_string(packed_error);
+  return ret == NULL ? "unknown library" : ret;
+}
+
 const char *ERR_func_error_string(uint32_t packed_error) {
   return "OPENSSL_internal";
 }
 
-const char *ERR_reason_error_string(uint32_t packed_error) {
+static const char *err_reason_error_string(uint32_t packed_error) {
   const uint32_t lib = ERR_GET_LIB(packed_error);
   const uint32_t reason = ERR_GET_REASON(packed_error);
 
@@ -579,6 +506,86 @@
                            kOpenSSLReasonValuesLen, kOpenSSLReasonStringData);
 }
 
+const char *ERR_reason_error_string(uint32_t packed_error) {
+  const char *ret = err_reason_error_string(packed_error);
+  return ret == NULL ? "unknown error" : ret;
+}
+
+char *ERR_error_string(uint32_t packed_error, char *ret) {
+  static char buf[ERR_ERROR_STRING_BUF_LEN];
+
+  if (ret == NULL) {
+    // TODO(fork): remove this.
+    ret = buf;
+  }
+
+#if !defined(NDEBUG)
+  // This is aimed to help catch callers who don't provide
+  // |ERR_ERROR_STRING_BUF_LEN| bytes of space.
+  OPENSSL_memset(ret, 0, ERR_ERROR_STRING_BUF_LEN);
+#endif
+
+  return ERR_error_string_n(packed_error, ret, ERR_ERROR_STRING_BUF_LEN);
+}
+
+char *ERR_error_string_n(uint32_t packed_error, char *buf, size_t len) {
+  if (len == 0) {
+    return NULL;
+  }
+
+  unsigned lib = ERR_GET_LIB(packed_error);
+  unsigned reason = ERR_GET_REASON(packed_error);
+
+  const char *lib_str = err_lib_error_string(packed_error);
+  const char *reason_str = err_reason_error_string(packed_error);
+
+  char lib_buf[64], reason_buf[64];
+  if (lib_str == NULL) {
+    BIO_snprintf(lib_buf, sizeof(lib_buf), "lib(%u)", lib);
+    lib_str = lib_buf;
+  }
+
+ if (reason_str == NULL) {
+    BIO_snprintf(reason_buf, sizeof(reason_buf), "reason(%u)", reason);
+    reason_str = reason_buf;
+  }
+
+  BIO_snprintf(buf, len, "error:%08" PRIx32 ":%s:OPENSSL_internal:%s",
+               packed_error, lib_str, reason_str);
+
+  if (strlen(buf) == len - 1) {
+    // output may be truncated; make sure we always have 5 colon-separated
+    // fields, i.e. 4 colons.
+    static const unsigned num_colons = 4;
+    unsigned i;
+    char *s = buf;
+
+    if (len <= num_colons) {
+      // In this situation it's not possible to ensure that the correct number
+      // of colons are included in the output.
+      return buf;
+    }
+
+    for (i = 0; i < num_colons; i++) {
+      char *colon = strchr(s, ':');
+      char *last_pos = &buf[len - 1] - num_colons + i;
+
+      if (colon == NULL || colon > last_pos) {
+        // set colon |i| at last possible position (buf[len-1] is the
+        // terminating 0). If we're setting this colon, then all whole of the
+        // rest of the string must be colons in order to have the correct
+        // number.
+        OPENSSL_memset(last_pos, ':', num_colons - i);
+        break;
+      }
+
+      s = colon + 1;
+    }
+  }
+
+  return buf;
+}
+
 void ERR_print_errors_cb(ERR_print_errors_callback_t callback, void *ctx) {
   char buf[ERR_ERROR_STRING_BUF_LEN];
   char buf2[1024];
diff --git a/crypto/err/err_test.cc b/crypto/err/err_test.cc
index 41bcc78..b41f8dd 100644
--- a/crypto/err/err_test.cc
+++ b/crypto/err/err_test.cc
@@ -283,3 +283,13 @@
   // A buffer length of zero should not touch the buffer.
   ERR_error_string_n(err, nullptr, 0);
 }
+
+// Error-printing functions should return something with unknown errors.
+TEST(ErrTest, UnknownError) {
+  uint32_t err = ERR_PACK(0xff, 0xfff);
+  EXPECT_TRUE(ERR_lib_error_string(err));
+  EXPECT_TRUE(ERR_reason_error_string(err));
+  char buf[128];
+  ERR_error_string_n(err, buf, sizeof(buf));
+  EXPECT_NE(0u, strlen(buf));
+}
diff --git a/include/openssl/err.h b/include/openssl/err.h
index 0960d80..572340c 100644
--- a/include/openssl/err.h
+++ b/include/openssl/err.h
@@ -223,11 +223,12 @@
                                         size_t len);
 
 // ERR_lib_error_string returns a string representation of the library that
-// generated |packed_error|.
+// generated |packed_error|, or a placeholder string is the library is
+// unrecognized.
 OPENSSL_EXPORT const char *ERR_lib_error_string(uint32_t packed_error);
 
 // ERR_reason_error_string returns a string representation of the reason for
-// |packed_error|.
+// |packed_error|, or a placeholder string if the reason is unrecognized.
 OPENSSL_EXPORT const char *ERR_reason_error_string(uint32_t packed_error);
 
 // ERR_print_errors_callback_t is the type of a function used by