ERR: special case system errors
authorRichard Levitte <levitte@openssl.org>
Mon, 29 Jun 2020 10:18:24 +0000 (12:18 +0200)
committerRichard Levitte <levitte@openssl.org>
Sun, 5 Jul 2020 19:13:03 +0000 (21:13 +0200)
Because system errors can be any positive number that fits in an 'int'
according to POSIX, we can't reasonably expect them to be in the 1..127
range, even though that's the most usual.

Instead of packing them into the OpenSSL error code structure, we
recognise them as a special case and mark them as such by storing them
in our error queue with the highest bit set.  We make OpenSSL specific
error records have their highest bit cleared, and in doing so, we
shift down the library section of the code by one bit.  This still
leaves a very large section for the reason codes.

Of course, we must adapt the error code and reason string extraction
and printing functions accordingly.

With this, we also thrown away the pre-loaded array of system error
strings, and extract them from the system when needed instead, i.e.
when we create error strings.

Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
(Merged from https://github.com/openssl/openssl/pull/12343)

crypto/err/err.c
crypto/err/err_local.h
crypto/err/err_prn.c
include/openssl/err.h

index 9a37c426252f33367039f0be4b548c2103024197..26cf2b0b9d3e4e2e9a887d0a2e340265cd0869d1 100644 (file)
@@ -173,84 +173,6 @@ static ERR_STRING_DATA *int_err_get_item(const ERR_STRING_DATA *d)
     return p;
 }
 
-#ifndef OPENSSL_NO_ERR
-/* 2019-05-21: Russian and Ukrainian locales on Linux require more than 6,5 kB */
-# define SPACE_SYS_STR_REASONS 8 * 1024
-# define NUM_SYS_STR_REASONS 127
-
-static ERR_STRING_DATA SYS_str_reasons[NUM_SYS_STR_REASONS + 1];
-/*
- * SYS_str_reasons is filled with copies of strerror() results at
- * initialization. 'errno' values up to 127 should cover all usual errors,
- * others will be displayed numerically by ERR_error_string. It is crucial
- * that we have something for each reason code that occurs in
- * ERR_str_reasons, or bogus reason strings will be returned for SYSerr(),
- * which always gets an errno value and never one of those 'standard' reason
- * codes.
- */
-
-static void build_SYS_str_reasons(void)
-{
-    /* OPENSSL_malloc cannot be used here, use static storage instead */
-    static char strerror_pool[SPACE_SYS_STR_REASONS];
-    char *cur = strerror_pool;
-    size_t cnt = 0;
-    static int init = 1;
-    int i;
-    int saveerrno = get_last_sys_error();
-
-    CRYPTO_THREAD_write_lock(err_string_lock);
-    if (!init) {
-        CRYPTO_THREAD_unlock(err_string_lock);
-        return;
-    }
-
-    for (i = 1; i <= NUM_SYS_STR_REASONS; i++) {
-        ERR_STRING_DATA *str = &SYS_str_reasons[i - 1];
-
-        str->error = ERR_PACK(ERR_LIB_SYS, 0, i);
-        /*
-         * If we have used up all the space in strerror_pool,
-         * there's no point in calling openssl_strerror_r()
-         */
-        if (str->string == NULL && cnt < sizeof(strerror_pool)) {
-            if (openssl_strerror_r(i, cur, sizeof(strerror_pool) - cnt)) {
-                size_t l = strlen(cur);
-
-                str->string = cur;
-                cnt += l;
-                cur += l;
-
-                /*
-                 * VMS has an unusual quirk of adding spaces at the end of
-                 * some (most? all?) messages. Lets trim them off.
-                 */
-                while (cur > strerror_pool && ossl_isspace(cur[-1])) {
-                    cur--;
-                    cnt--;
-                }
-                *cur++ = '\0';
-                cnt++;
-            }
-        }
-        if (str->string == NULL)
-            str->string = "unknown";
-    }
-
-    /*
-     * Now we still have SYS_str_reasons[NUM_SYS_STR_REASONS] = {0, NULL}, as
-     * required by ERR_load_strings.
-     */
-
-    init = 0;
-
-    CRYPTO_THREAD_unlock(err_string_lock);
-    /* openssl_strerror_r could change errno, but we want to preserve it */
-    set_sys_error(saveerrno);
-    err_load_strings(SYS_str_reasons);
-}
-#endif
-
 static void ERR_STATE_free(ERR_STATE *s)
 {
     int i;
@@ -322,7 +244,6 @@ int ERR_load_ERR_strings(void)
 
     err_load_strings(ERR_str_libraries);
     err_load_strings(ERR_str_reasons);
-    build_SYS_str_reasons();
 #endif
     return 1;
 }
@@ -569,8 +490,8 @@ static unsigned long get_error_values(ERR_GET_ACTION g,
 
 void ERR_error_string_n(unsigned long e, char *buf, size_t len)
 {
-    char lsbuf[64], rsbuf[64];
-    const char *ls, *rs;
+    char lsbuf[64], rsbuf[256];
+    const char *ls, *rs = NULL;
     unsigned long f = 0, l, r;
 
     if (len == 0)
@@ -583,8 +504,19 @@ void ERR_error_string_n(unsigned long e, char *buf, size_t len)
         ls = lsbuf;
     }
 
-    rs = ERR_reason_error_string(e);
+    /*
+     * ERR_reason_error_string() can't safely return system error strings,
+     * since it would call openssl_strerror_r(), which needs a buffer for
+     * thread safety.  So for system errors, we call openssl_strerror_r()
+     * directly instead.
+     */
     r = ERR_GET_REASON(e);
+    if (ERR_SYSTEM_ERROR(e)) {
+        if (openssl_strerror_r(r, rsbuf, sizeof(rsbuf)))
+            rs = rsbuf;
+    } else {
+        rs = ERR_reason_error_string(e);
+    }
     if (rs == NULL) {
         BIO_snprintf(rsbuf, sizeof(rsbuf), "reason(%lu)", r);
         rs = rsbuf;
@@ -642,6 +574,14 @@ const char *ERR_reason_error_string(unsigned long e)
         return NULL;
     }
 
+    /*
+     * ERR_reason_error_string() can't safely return system error strings,
+     * since openssl_strerror_r() needs a buffer for thread safety, and we
+     * haven't got one that would serve any sensible purpose.
+     */
+    if (ERR_SYSTEM_ERROR(e))
+        return NULL;
+
     l = ERR_GET_LIB(e);
     r = ERR_GET_REASON(e);
     d.error = ERR_PACK(l, 0, r);
index 0374bf6a6f856420861d5d22189171d81ab4f671..add49af44c3217d39e96d39bef8a42522993f32d 100644 (file)
@@ -38,7 +38,10 @@ static ossl_inline void err_clear_data(ERR_STATE *es, size_t i, int deall)
 static ossl_inline void err_set_error(ERR_STATE *es, size_t i,
                                       int lib, int reason)
 {
-    es->err_buffer[i] = ERR_PACK(lib, 0, reason);
+    es->err_buffer[i] =
+        lib == ERR_LIB_SYS
+        ? (unsigned int)(ERR_SYSTEM_FLAG |  reason)
+        : ERR_PACK(lib, 0, reason);
 }
 
 static ossl_inline void err_set_debug(ERR_STATE *es, size_t i,
index 80cc0ecf1a3d6fce68e8fa763f535d941b9a99ab..f67cf2e32b695929a9842bd3bcfced7a398452da 100644 (file)
@@ -23,16 +23,36 @@ void ERR_print_errors_cb(int (*cb) (const char *str, size_t len, void *u),
 {
     CRYPTO_THREAD_ID tid = CRYPTO_THREAD_get_current_id();
     unsigned long l;
-    char buf[ERR_PRINT_BUF_SIZE], *hex;
-    const char *lib, *reason;
     const char *file, *data, *func;
     int line, flags;
 
     while ((l = ERR_get_error_all(&file, &line, &func, &data, &flags)) != 0) {
+        char buf[ERR_PRINT_BUF_SIZE], *hex;
+        const char *lib, *reason = NULL;
+        char rsbuf[256];
+        unsigned long r = ERR_GET_REASON(l);
+
         lib = ERR_lib_error_string(l);
-        reason = ERR_reason_error_string(l);
+
+        /*
+         * ERR_reason_error_string() can't safely return system error strings,
+         * since it would call openssl_strerror_r(), which needs a buffer for
+         * thread safety.  So for system errors, we call openssl_strerror_r()
+         * directly instead.
+         */
+        if (ERR_SYSTEM_ERROR(l)) {
+            if (openssl_strerror_r(r, rsbuf, sizeof(rsbuf)))
+                reason = rsbuf;
+        } else {
+            reason = ERR_reason_error_string(l);
+        }
+
         if (func == NULL)
             func = "unknown function";
+        if (reason == NULL) {
+            BIO_snprintf(rsbuf, sizeof(rsbuf), "reason(%lu)", r);
+            reason = rsbuf;
+        }
         if ((flags & ERR_TXT_STRING) == 0)
             data = "";
         hex = openssl_buf2hexstr_sep((const unsigned char *)&tid, sizeof(tid),
index 66a8f480285d9ab54c2130ba16272277cd0e0463..a40d231ea098944ea549dec42bf9802675e1c8e9 100644 (file)
@@ -39,6 +39,7 @@ extern "C" {
 #  endif
 # endif
 
+# include <limits.h>
 # include <errno.h>
 
 # define ERR_TXT_MALLOCED        0x01
@@ -163,43 +164,95 @@ struct err_state_st {
 #  define X509err(f, r) ERR_raise_data(ERR_LIB_X509, (r), NULL)
 # endif
 
-/*
- * The error code currently packs as follows (viewed as hex nibbles):
+/*-
+ * The error code packs differently depending on if it records a system
+ * error or an OpenSSL error.
  *
- * LL rRRRRR
+ * A system error packs like this (we follow POSIX and only allow positive
+ * numbers that fit in an |int|):
  *
- * Where LL is the library code, r is the reason flags, and rRRRRR is the
- * reason code.
- * Do note that the reason flags is part of the reason code, and could as
- * well be seen as a section of all possible reason codes.  We do this for
- * backward compatibility reasons, i.e. how ERR_R_FATAL was implemented.
+ * +-+-------------------------------------------------------------+
+ * |1|                     system error number                     |
+ * +-+-------------------------------------------------------------+
+ *
+ * An OpenSSL error packs like this:
  *
- * System errors (ERR_LIB_SYS) are structured the same way, except they
- * don't have any reason flag.
+ * <---------------------------- 32 bits -------------------------->
+ *    <--- 8 bits ---><------------------ 23 bits ----------------->
+ * +-+---------------+---------------------------------------------+
+ * |0|    library    |                    reason                   |
+ * +-+---------------+---------------------------------------------+
  *
- * LL RRRRRR
+ * A few of the reason bits are reserved as flags with special meaning:
+ *
+ *                    <4 bits><-------------- 19 bits ------------->
+ *                   +-------+-------------------------------------+
+ *                   | rflags|                reason               |
+ *                   +-------+-------------------------------------+
+ *
+ * We have the reason flags being part of the overall reason code for
+ * backward compatibility reasons, i.e. how ERR_R_FATAL was implemented.
  */
-# define ERR_LIB_OFFSET     24L
-# define ERR_LIB_MASK       0xFF
-# define ERR_RFLAGS_OFFSET  20L
-# define ERR_RFLAGS_MASK    0xF
-# define ERR_REASON_MASK    0XFFFFFF
+
+/* Macros to help decode recorded system errors */
+# define ERR_SYSTEM_FLAG                ((unsigned int)INT_MAX + 1)
+# define ERR_SYSTEM_MASK                ((unsigned int)INT_MAX)
+
+/* Macros to help decode recorded OpenSSL errors */
+# define ERR_LIB_OFFSET                 23L
+# define ERR_LIB_MASK                   0xFF
+# define ERR_RFLAGS_OFFSET              19L
+# define ERR_RFLAGS_MASK                0xF
+# define ERR_REASON_MASK                0X7FFFFF
 
 /*
  * Reason flags are defined pre-shifted to easily combine with the reason
  * number.
  */
-# define ERR_RFLAG_FATAL    (0x1 << ERR_RFLAGS_OFFSET)
-
-/* ERR_PACK takes reason flags and reason code combined in |r| */
-# define ERR_PACK(l,f,r)                                                \
-    ( (((unsigned int)(l) & ERR_LIB_MASK) << ERR_LIB_OFFSET) |          \
-      (((unsigned int)(r) & ERR_REASON_MASK)) )
-# define ERR_GET_LIB(l)     (int)(((l) >> ERR_LIB_OFFSET) & ERR_LIB_MASK)
-# define ERR_GET_FUNC(l)    0
-# define ERR_GET_RFLAGS(l)  (int)((l) & (ERR_RFLAGS_MASK << ERR_RFLAGS_OFFSET))
-# define ERR_GET_REASON(l)  (int)((l) & ERR_REASON_MASK)
-# define ERR_FATAL_ERROR(l) (int)((l) & ERR_RFLAG_FATAL)
+# define ERR_RFLAG_FATAL                (0x1 << ERR_RFLAGS_OFFSET)
+
+# define ERR_SYSTEM_ERROR(errcode)      (((errcode) & ERR_SYSTEM_FLAG) != 0)
+
+static ossl_inline int ERR_GET_LIB(unsigned long errcode)
+{
+    if (ERR_SYSTEM_ERROR(errcode))
+        return ERR_LIB_SYS;
+    return (errcode >> ERR_LIB_OFFSET) & ERR_LIB_MASK;
+}
+
+static ossl_inline int ERR_GET_FUNC(unsigned long errcode)
+{
+    return 0;
+}
+
+static ossl_inline int ERR_GET_RFLAGS(unsigned long errcode)
+{
+    if (ERR_SYSTEM_ERROR(errcode))
+        return 0;
+    return errcode & (ERR_RFLAGS_MASK << ERR_RFLAGS_OFFSET);
+}
+
+static ossl_inline int ERR_GET_REASON(unsigned long errcode)
+{
+    if (ERR_SYSTEM_ERROR(errcode))
+        return errcode & ERR_SYSTEM_MASK;
+    return errcode & ERR_REASON_MASK;
+}
+
+static ossl_inline int ERR_FATAL_ERROR(unsigned long errcode)
+{
+    return (ERR_GET_RFLAGS(errcode) & ERR_RFLAG_FATAL) != 0;
+}
+
+/*
+ * ERR_PACK is a helper macro to properly pack OpenSSL error codes and may
+ * only be used for that purpose.  System errors are packed internally.
+ * ERR_PACK takes reason flags and reason code combined in |reason|.
+ * ERR_PACK ignores |func|, that parameter is just legacy from pre-3.0 OpenSSL.
+ */
+# define ERR_PACK(lib,func,reason)                                      \
+    ( (((unsigned long)(lib)    & ERR_LIB_MASK   ) << ERR_LIB_OFFSET) | \
+      (((unsigned long)(reason) & ERR_REASON_MASK)) )
 
 # ifndef OPENSSL_NO_DEPRECATED_3_0
 #  define SYS_F_FOPEN             0