Repository: aws/s2n-tls (C)

Issue:
Allow s2n_strerror_debug output without unecessary filepath prefixed
### Problem:

`s2n_strerror_debug` returns a debug string that contains a line number in s2n where the error occurred. This line number can contain excessive file path information that results in a string that is longer than necessary to debug information in s2n. For example: `Error encountered in /not/useful/file/path/prefix/for/the/error/tls/s2n_recv.c:53`

The file path information can create larger log lines than necessary.

### Solution:

Allow a build or runtime switch to only write the s2n source file *name* and line number. The path information is not necessary, assuming s2n does not repeat file names in source code.

* **Does this change what S2N sends over the wire?** No
* **Does this change any public APIs?** Maybe
* **Which versions of TLS will this impact?** N/A

### Requirements / Acceptance Criteria:

Solution must have an option the user can set during build or runtime to get a s2n_strerror_debug output that does not have extra file path prefixes in it.




Fix (patch):
diff --git a/api/s2n.h b/api/s2n.h
index 464c7a13151..9416cd38ceb 100644
--- a/api/s2n.h
+++ b/api/s2n.h
@@ -115,6 +115,7 @@ extern "C" {
  * explaining the error in English by calling s2n_strerror(s2n_errno, "EN").
  * A string containing human readable error name; can be generated with `s2n_strerror_name`.
  * A string containing internal debug information, including filename and line number, can be generated with `s2n_strerror_debug`.
+ * A string containing only the filename and line number can be generated with `s2n_strerror_source`.
  * This string is useful to include when reporting issues to the s2n-tls development team.
  *
  * @warning To avoid possible confusion, s2n_errno should be cleared after processing an error: `s2n_errno = S2N_ERR_T_OK`
@@ -407,6 +408,14 @@ S2N_API extern const char *s2n_strerror_debug(int error, const char *lang);
  */
 S2N_API extern const char *s2n_strerror_name(int error);
 
+/**
+ * Translates an s2n_error code to a filename and line number.
+ *
+ * @param error The error code to explain. Usually this is s2n_errno.
+ * @returns The error string.
+ */
+S2N_API extern const char *s2n_strerror_source(int error);
+
 /**
  * Opaque stack trace structure.
  */
diff --git a/error/s2n_errno.c b/error/s2n_errno.c
index 5e41aa5289b..1d5e57fb6ee 100644
--- a/error/s2n_errno.c
+++ b/error/s2n_errno.c
@@ -30,7 +30,7 @@
 #endif
 
 __thread int s2n_errno;
-__thread const char *s2n_debug_str;
+__thread struct s2n_debug_info _s2n_debug_info = { .debug_str = "", .source = "" };
 
 /**
  * Returns the address of the thread-local `s2n_errno` variable
@@ -378,7 +378,17 @@ const char *s2n_strerror_debug(int error, const char *lang)
         return s2n_strerror(error, lang);
     }
 
-    return s2n_debug_str;
+    return _s2n_debug_info.debug_str;
+}
+
+const char *s2n_strerror_source(int error)
+{
+    /* No error, just return the no error string */
+    if (error == S2N_ERR_OK) {
+        return s2n_strerror(error, "EN");
+    }
+
+    return _s2n_debug_info.source;
 }
 
 int s2n_error_get_type(int error)
@@ -400,6 +410,12 @@ int s2n_stack_traces_enabled_set(bool newval)
     return S2N_SUCCESS;
 }
 
+void s2n_debug_info_reset(void)
+{
+    _s2n_debug_info.debug_str = "";
+    _s2n_debug_info.source = "";
+}
+
 #ifdef S2N_STACKTRACE
 
     #define MAX_BACKTRACE_DEPTH 20
diff --git a/error/s2n_errno.h b/error/s2n_errno.h
index 93b17d9189b..5b97bd8aa7c 100644
--- a/error/s2n_errno.h
+++ b/error/s2n_errno.h
@@ -17,6 +17,7 @@
 
 #include <stdbool.h>
 #include <stdio.h>
+#include <string.h>
 
 #include "api/s2n.h"
 #include "utils/s2n_ensure.h"
@@ -320,18 +321,36 @@ typedef enum {
 } s2n_error;
 
 #define S2N_DEBUG_STR_LEN 128
-extern __thread const char *s2n_debug_str;
+
+struct s2n_debug_info {
+    const char *debug_str;
+    const char *source;
+};
+
+extern __thread struct s2n_debug_info _s2n_debug_info;
 
 #define TO_STRING(s)   #s
 #define STRING_(s)     TO_STRING(s)
 #define STRING__LINE__ STRING_(__LINE__)
 
-#define _S2N_DEBUG_LINE "Error encountered in " __FILE__ ":" STRING__LINE__
-#define _S2N_ERROR(x)                    \
-    do {                                 \
-        s2n_debug_str = _S2N_DEBUG_LINE; \
-        s2n_errno = (x);                 \
-        s2n_calculate_stacktrace();      \
+/* gets the basename of a file path */
+/* _S2N_EXTRACT_BASENAME("Error encountered in /path/to/my/file.c") -> "file.c" */
+#if !(defined(CBMC) || defined(__TIMING_CONTRACTS__))
+    #define _S2N_RSPLIT(subject, c)     (strrchr((subject), c) ? strrchr((subject), c) + 1 : (subject))
+    #define _S2N_EXTRACT_BASENAME(path) _S2N_RSPLIT((path) + strlen(_S2N_DEBUG_LINE_PREFIX), '/')
+#else
+    #define _S2N_EXTRACT_BASENAME(path) path
+#endif
+
+#define _S2N_DEBUG_LINE_PREFIX "Error encountered in "
+#define _S2N_DEBUG_LINE        _S2N_DEBUG_LINE_PREFIX __FILE__ ":" STRING__LINE__
+
+#define _S2N_ERROR(x)                                                              \
+    do {                                                                           \
+        _s2n_debug_info.debug_str = _S2N_DEBUG_LINE;                               \
+        _s2n_debug_info.source = _S2N_EXTRACT_BASENAME(_s2n_debug_info.debug_str); \
+        s2n_errno = (x);                                                           \
+        s2n_calculate_stacktrace();                                                \
     } while (0)
 #define S2N_ERROR_PRESERVE_ERRNO() \
     do {                           \
@@ -374,3 +393,4 @@ int s2n_calculate_stacktrace(void);
 int s2n_print_stacktrace(FILE *fptr);
 int s2n_free_stacktrace(void);
 int s2n_get_stacktrace(struct s2n_stacktrace *trace);
+void s2n_debug_info_reset(void);
diff --git a/scripts/s2n_safety_macros.py b/scripts/s2n_safety_macros.py
index ada7107d29f..157b6170dd3 100644
--- a/scripts/s2n_safety_macros.py
+++ b/scripts/s2n_safety_macros.py
@@ -537,7 +537,7 @@ def cmp_check(op):
         * The size of the data pointed to by both the `destination` and `source` parameters,
           shall be at least `len` bytes.
         ''',
-        impl = '__S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), {prefix}GUARD_PTR)',
+        impl = '__S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), {prefix}ENSURE_REF)',
         harness = '''
         static {ret} {prefix}CHECKED_MEMCPY_harness(uint32_t* dest, uint32_t* source, size_t len)
         {{
diff --git a/tls/s2n_handshake_io.c b/tls/s2n_handshake_io.c
index 6bc5d16d125..79cbd6a5e26 100644
--- a/tls/s2n_handshake_io.c
+++ b/tls/s2n_handshake_io.c
@@ -1046,7 +1046,7 @@ int s2n_conn_set_handshake_type(struct s2n_connection *conn)
 
     if (conn->config->use_tickets) {
         if (conn->session_ticket_status == S2N_DECRYPT_TICKET) {
-            /* We reuse the session if a valid TLS12 ticket is provided. 
+            /* We reuse the session if a valid TLS12 ticket is provided.
              * Otherwise, we will perform a full handshake and then generate
              * a new session ticket. */
             if (s2n_decrypt_session_ticket(conn, &conn->client_ticket_to_decrypt) == S2N_SUCCESS) {
@@ -1639,7 +1639,7 @@ int s2n_negotiate_impl(struct s2n_connection *conn, s2n_blocked_status *blocked)
                     /* Non-retryable write error. The peer might have sent an alert. Try and read it. */
                     const int write_errno = errno;
                     const int write_s2n_errno = s2n_errno;
-                    const char *write_s2n_debug_str = s2n_debug_str;
+                    struct s2n_debug_info write_s2n_debug_info = _s2n_debug_info;
 
                     if (s2n_handshake_read_io(conn) < 0 && s2n_errno == S2N_ERR_ALERT) {
                         /* s2n_handshake_read_io has set s2n_errno */
@@ -1648,7 +1648,7 @@ int s2n_negotiate_impl(struct s2n_connection *conn, s2n_blocked_status *blocked)
                         /* Let the write error take precedence if we didn't read an alert. */
                         errno = write_errno;
                         s2n_errno = write_s2n_errno;
-                        s2n_debug_str = write_s2n_debug_str;
+                        _s2n_debug_info = write_s2n_debug_info;
                         S2N_ERROR_PRESERVE_ERRNO();
                     }
                 }
diff --git a/utils/s2n_ensure.c b/utils/s2n_ensure.c
index ccaf013309e..5824f9b6186 100644
--- a/utils/s2n_ensure.c
+++ b/utils/s2n_ensure.c
@@ -15,14 +15,13 @@
 
 #include "utils/s2n_safety.h"
 
-void *s2n_ensure_memcpy_trace(void *restrict to, const void *restrict from, size_t size, const char *debug_str)
+void *s2n_ensure_memcpy_trace(void *restrict to, const void *restrict from, size_t size)
 {
-    if (to == NULL || from == NULL) {
-        s2n_errno = S2N_ERR_NULL;
-        s2n_debug_str = debug_str;
-        return NULL;
-    }
+    PTR_ENSURE_REF(to);
+    PTR_ENSURE_REF(from);
 
     /* use memmove instead of memcpy since it'll handle overlapping regions and not result in UB */
-    return memmove(to, from, size);
+    void *result = memmove(to, from, size);
+    PTR_ENSURE_REF(result);
+    return result;
 }
diff --git a/utils/s2n_ensure.h b/utils/s2n_ensure.h
index 4fe117f14db..c072d93e952 100644
--- a/utils/s2n_ensure.h
+++ b/utils/s2n_ensure.h
@@ -60,13 +60,13 @@
     #define __S2N_ENSURE_POSTCONDITION(result) (s2n_likely(s2n_result_is_ok(result)) ? S2N_RESULT_OK : S2N_RESULT_ERROR)
 #endif
 
-#define __S2N_ENSURE_SAFE_MEMCPY(d, s, n, guard)                                     \
-    do {                                                                             \
-        __typeof(n) __tmp_n = (n);                                                   \
-        if (s2n_likely(__tmp_n)) {                                                   \
-            void *r = s2n_ensure_memcpy_trace((d), (s), (__tmp_n), _S2N_DEBUG_LINE); \
-            guard(r);                                                                \
-        }                                                                            \
+#define __S2N_ENSURE_SAFE_MEMCPY(d, s, n, guard)                    \
+    do {                                                            \
+        __typeof(n) __tmp_n = (n);                                  \
+        if (s2n_likely(__tmp_n)) {                                  \
+            void *r = s2n_ensure_memcpy_trace((d), (s), (__tmp_n)); \
+            guard(r);                                               \
+        }                                                           \
     } while (0)
 
 #define __S2N_ENSURE_SAFE_MEMSET(d, c, n, guard) \
@@ -103,9 +103,9 @@
  *
  */
 #if defined(S2N___RESTRICT__SUPPORTED)
-void *s2n_ensure_memcpy_trace(void *__restrict__ to, const void *__restrict__ from, size_t size, const char *debug_str);
+void *s2n_ensure_memcpy_trace(void *__restrict__ to, const void *__restrict__ from, size_t size);
 #else
-void *s2n_ensure_memcpy_trace(void *restrict to, const void *restrict from, size_t size, const char *debug_str);
+void *s2n_ensure_memcpy_trace(void *restrict to, const void *restrict from, size_t size);
 #endif
 
 /**
@@ -152,7 +152,7 @@ void *s2n_ensure_memcpy_trace(void *restrict to, const void *restrict from, size
  */
 #ifdef CBMC
     #define CONTRACT_ASSIGNS(...)     __CPROVER_assigns(__VA_ARGS__)
-    #define CONTRACT_ASSIGNS_ERR(...) CONTRACT_ASSIGNS(__VA_ARGS__, s2n_debug_str, s2n_errno)
+    #define CONTRACT_ASSIGNS_ERR(...) CONTRACT_ASSIGNS(__VA_ARGS__, _s2n_debug_info, s2n_errno)
     #define CONTRACT_REQUIRES(...)    __CPROVER_requires(__VA_ARGS__)
     #define CONTRACT_ENSURES(...)     __CPROVER_ensures(__VA_ARGS__)
     #define CONTRACT_INVARIANT(...)   __CPROVER_loop_invariant(__VA_ARGS__)
diff --git a/utils/s2n_safety_macros.h b/utils/s2n_safety_macros.h
index 41d8a88fab4..33e5133dca4 100644
--- a/utils/s2n_safety_macros.h
+++ b/utils/s2n_safety_macros.h
@@ -161,7 +161,7 @@
  * * The size of the data pointed to by both the `destination` and `source` parameters,
  *   shall be at least `len` bytes.
  */
-#define RESULT_CHECKED_MEMCPY(destination, source, len)        __S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), RESULT_GUARD_PTR)
+#define RESULT_CHECKED_MEMCPY(destination, source, len)        __S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), RESULT_ENSURE_REF)
 
 /**
  * Performs a safer memset
@@ -357,7 +357,7 @@
  * * The size of the data pointed to by both the `destination` and `source` parameters,
  *   shall be at least `len` bytes.
  */
-#define POSIX_CHECKED_MEMCPY(destination, source, len)        __S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), POSIX_GUARD_PTR)
+#define POSIX_CHECKED_MEMCPY(destination, source, len)        __S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), POSIX_ENSURE_REF)
 
 /**
  * DEPRECATED: all methods (except those in s2n.h) should return s2n_result.
@@ -563,7 +563,7 @@
  * * The size of the data pointed to by both the `destination` and `source` parameters,
  *   shall be at least `len` bytes.
  */
-#define PTR_CHECKED_MEMCPY(destination, source, len)          __S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), PTR_GUARD)
+#define PTR_CHECKED_MEMCPY(destination, source, len)          __S2N_ENSURE_SAFE_MEMCPY((destination), (source), (len), PTR_ENSURE_REF)
 
 /**
  * DEPRECATED: all methods (except those in s2n.h) should return s2n_result.