diff --git a/crypto/err/err.c b/crypto/err/err.c index a620fc7e..c7bff168 100644 --- a/crypto/err/err.c +++ b/crypto/err/err.c @@ -123,7 +123,38 @@ OPENSSL_MSVC_PRAGMA(warning(pop)) #include #include "../internal.h" +#include "./internal.h" + + +struct err_error_st { + // file contains the filename where the error occurred. + const char *file; + // data contains a NUL-terminated string with optional data. It must be freed + // with |OPENSSL_free|. + char *data; + // packed contains the error library and reason, as packed by ERR_PACK. + uint32_t packed; + // line contains the line number where the error occurred. + uint16_t line; + // mark indicates a reversion point in the queue. See |ERR_pop_to_mark|. + unsigned mark : 1; +}; +// ERR_STATE contains the per-thread, error queue. +typedef struct err_state_st { + // errors contains the ERR_NUM_ERRORS most recent errors, organised as a ring + // buffer. + struct err_error_st errors[ERR_NUM_ERRORS]; + // top contains the index one past the most recent error. If |top| equals + // |bottom| then the queue is empty. + unsigned top; + // bottom contains the index of the last error in the queue. + unsigned bottom; + + // to_free, if not NULL, contains a pointer owned by this structure that was + // previously a |data| pointer of one of the elements of |errors|. + void *to_free; +} ERR_STATE; extern const uint32_t kOpenSSLReasonValues[]; extern const size_t kOpenSSLReasonValuesLen; @@ -135,6 +166,16 @@ static void err_clear(struct err_error_st *error) { OPENSSL_memset(error, 0, sizeof(struct err_error_st)); } +static void err_copy(struct err_error_st *dst, const struct err_error_st *src) { + err_clear(dst); + dst->file = src->file; + if (src->data != NULL) { + dst->data = OPENSSL_strdup(src->data); + } + dst->packed = src->packed; + dst->line = src->line; +} + // global_next_library contains the next custom library value to return. static int global_next_library = ERR_NUM_LIBS; @@ -150,8 +191,7 @@ static void err_state_free(void *statep) { return; } - unsigned i; - for (i = 0; i < ERR_NUM_ERRORS; i++) { + for (unsigned i = 0; i < ERR_NUM_ERRORS; i++) { err_clear(&state->errors[i]); } OPENSSL_free(state->to_free); @@ -740,3 +780,68 @@ void ERR_free_strings(void) {} void ERR_load_BIO_strings(void) {} void ERR_load_ERR_strings(void) {} + +struct err_save_state_st { + struct err_error_st *errors; + size_t num_errors; +}; + +void ERR_SAVE_STATE_free(ERR_SAVE_STATE *state) { + if (state == NULL) { + return; + } + for (size_t i = 0; i < state->num_errors; i++) { + err_clear(&state->errors[i]); + } + OPENSSL_free(state->errors); + OPENSSL_free(state); +} + +ERR_SAVE_STATE *ERR_save_state(void) { + ERR_STATE *const state = err_get_state(); + if (state == NULL || state->top == state->bottom) { + return NULL; + } + + ERR_SAVE_STATE *ret = OPENSSL_malloc(sizeof(ERR_SAVE_STATE)); + if (ret == NULL) { + return NULL; + } + + // Errors are stored in the range (bottom, top]. + size_t num_errors = state->top >= state->bottom + ? state->top - state->bottom + : ERR_NUM_ERRORS + state->top - state->bottom; + assert(num_errors < ERR_NUM_ERRORS); + ret->errors = OPENSSL_malloc(num_errors * sizeof(struct err_error_st)); + if (ret->errors == NULL) { + OPENSSL_free(ret); + return NULL; + } + OPENSSL_memset(ret->errors, 0, num_errors * sizeof(struct err_error_st)); + ret->num_errors = num_errors; + + for (size_t i = 0; i < num_errors; i++) { + size_t j = (state->bottom + i + 1) % ERR_NUM_ERRORS; + err_copy(&ret->errors[i], &state->errors[j]); + } + return ret; +} + +void ERR_restore_state(const ERR_SAVE_STATE *state) { + if (state == NULL || state->num_errors == 0) { + ERR_clear_error(); + return; + } + + ERR_STATE *const dst = err_get_state(); + if (dst == NULL) { + return; + } + + for (size_t i = 0; i < state->num_errors; i++) { + err_copy(&dst->errors[i], &state->errors[i]); + } + dst->top = state->num_errors - 1; + dst->bottom = ERR_NUM_ERRORS - 1; +} diff --git a/crypto/err/err_test.cc b/crypto/err/err_test.cc index 5d04ae2a..489d2486 100644 --- a/crypto/err/err_test.cc +++ b/crypto/err/err_test.cc @@ -21,6 +21,8 @@ #include #include +#include "./internal.h" + TEST(ErrTest, Overflow) { for (unsigned i = 0; i < ERR_NUM_ERRORS*2; i++) { @@ -119,3 +121,94 @@ TEST(ErrTest, PutMacro) { EXPECT_EQ(ERR_LIB_USER, ERR_GET_LIB(error)); EXPECT_EQ(ERR_R_INTERNAL_ERROR, ERR_GET_REASON(error)); } + +TEST(ErrTest, SaveAndRestore) { + // Restoring no state clears the error queue, including error data. + ERR_put_error(1, 0 /* unused */, 1, "test1.c", 1); + ERR_put_error(2, 0 /* unused */, 2, "test2.c", 2); + ERR_add_error_data(1, "data1"); + ERR_restore_state(nullptr); + EXPECT_EQ(0u, ERR_get_error()); + + // Add some entries to the error queue and save it. + ERR_put_error(1, 0 /* unused */, 1, "test1.c", 1); + ERR_add_error_data(1, "data1"); + ERR_put_error(2, 0 /* unused */, 2, "test2.c", 2); + ERR_put_error(3, 0 /* unused */, 3, "test3.c", 3); + ERR_add_error_data(1, "data3"); + bssl::UniquePtr saved(ERR_save_state()); + ASSERT_TRUE(saved); + + // The existing error queue entries still exist. + int line, flags; + const char *file, *data; + uint32_t packed_error = ERR_get_error_line_data(&file, &line, &data, &flags); + EXPECT_EQ(ERR_GET_LIB(packed_error), 1); + EXPECT_EQ(ERR_GET_REASON(packed_error), 1); + EXPECT_STREQ("test1.c", file); + EXPECT_EQ(line, 1); + EXPECT_STREQ(data, "data1"); + EXPECT_EQ(flags, ERR_FLAG_STRING); + + // The state may be restored, both over an empty and non-empty state. + for (unsigned i = 0; i < 2; i++) { + SCOPED_TRACE(i); + ERR_restore_state(saved.get()); + + packed_error = ERR_get_error_line_data(&file, &line, &data, &flags); + EXPECT_EQ(ERR_GET_LIB(packed_error), 1); + EXPECT_EQ(ERR_GET_REASON(packed_error), 1); + EXPECT_STREQ("test1.c", file); + EXPECT_EQ(line, 1); + EXPECT_STREQ(data, "data1"); + EXPECT_EQ(flags, ERR_FLAG_STRING); + + packed_error = ERR_get_error_line_data(&file, &line, &data, &flags); + EXPECT_EQ(ERR_GET_LIB(packed_error), 2); + EXPECT_EQ(ERR_GET_REASON(packed_error), 2); + EXPECT_STREQ("test2.c", file); + EXPECT_EQ(line, 2); + EXPECT_STREQ(data, ""); // No error data is reported as the empty string. + EXPECT_EQ(flags, 0); + + packed_error = ERR_get_error_line_data(&file, &line, &data, &flags); + EXPECT_EQ(ERR_GET_LIB(packed_error), 3); + EXPECT_EQ(ERR_GET_REASON(packed_error), 3); + EXPECT_STREQ("test3.c", file); + EXPECT_EQ(line, 3); + EXPECT_STREQ(data, "data3"); + EXPECT_EQ(flags, ERR_FLAG_STRING); + + // The error queue is now empty for the next iteration. + EXPECT_EQ(0u, ERR_get_error()); + } + + // Test a case where the error queue wraps around. The first set of errors + // will all be discarded, but result in wrapping the list around. + ERR_clear_error(); + for (unsigned i = 0; i < ERR_NUM_ERRORS / 2; i++) { + ERR_put_error(0, 0 /* unused */, 0, "invalid", 0); + } + for (unsigned i = 1; i < ERR_NUM_ERRORS; i++) { + ERR_put_error(i, 0 /* unused */, i, "test", i); + } + saved.reset(ERR_save_state()); + + // The state may be restored, both over an empty and non-empty state. Pop one + // error off so the first iteration is tested to not be a no-op. + ERR_get_error(); + for (int i = 0; i < 2; i++) { + SCOPED_TRACE(i); + ERR_restore_state(saved.get()); + for (int j = 1; j < ERR_NUM_ERRORS; j++) { + SCOPED_TRACE(j); + packed_error = ERR_get_error_line_data(&file, &line, &data, &flags); + EXPECT_EQ(ERR_GET_LIB(packed_error), j); + EXPECT_EQ(ERR_GET_REASON(packed_error), j); + EXPECT_STREQ("test", file); + EXPECT_EQ(line, j); + } + // The error queue is now empty for the next iteration. + EXPECT_EQ(0u, ERR_get_error()); + } +} diff --git a/crypto/err/internal.h b/crypto/err/internal.h new file mode 100644 index 00000000..3f2397c1 --- /dev/null +++ b/crypto/err/internal.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2017, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#ifndef OPENSSL_HEADER_CRYPTO_ERR_INTERNAL_H +#define OPENSSL_HEADER_CRYPTO_ERR_INTERNAL_H + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + + +// Private error queue functions. + +// ERR_SAVE_STATE contains a saved representation of the error queue. It is +// slightly more compact than |ERR_STATE| as the error queue will typically not +// contain |ERR_NUM_ERRORS| entries. +typedef struct err_save_state_st ERR_SAVE_STATE; + +// ERR_SAVE_STATE_free releases all memory associated with |state|. +OPENSSL_EXPORT void ERR_SAVE_STATE_free(ERR_SAVE_STATE *state); + +// ERR_save_state returns a newly-allocated |ERR_SAVE_STATE| structure +// containing the current state of the error queue or NULL on allocation +// error. It should be released with |ERR_SAVE_STATE_free|. +OPENSSL_EXPORT ERR_SAVE_STATE *ERR_save_state(void); + +// ERR_restore_state clears the error queue and replaces it with |state|. +OPENSSL_EXPORT void ERR_restore_state(const ERR_SAVE_STATE *state); + + +#if defined(__cplusplus) +} // extern C + +extern "C++" { + +namespace bssl { + +BORINGSSL_MAKE_DELETER(ERR_SAVE_STATE, ERR_SAVE_STATE_free) + +} // namespace bssl + +} // extern C++ +#endif + +#endif // OPENSSL_HEADER_CRYPTO_ERR_INTERNAL_H diff --git a/include/openssl/err.h b/include/openssl/err.h index 5de3cbd7..9a65ffb6 100644 --- a/include/openssl/err.h +++ b/include/openssl/err.h @@ -436,39 +436,10 @@ OPENSSL_EXPORT void ERR_add_error_data(unsigned count, ...); OPENSSL_EXPORT void ERR_add_error_dataf(const char *format, ...) OPENSSL_PRINTF_FORMAT_FUNC(1, 2); -struct err_error_st { - // file contains the filename where the error occurred. - const char *file; - // data contains a NUL-terminated string with optional data. It must be freed - // with |OPENSSL_free|. - char *data; - // packed contains the error library and reason, as packed by ERR_PACK. - uint32_t packed; - // line contains the line number where the error occurred. - uint16_t line; - // mark indicates a reversion point in the queue. See |ERR_pop_to_mark|. - unsigned mark : 1; -}; - -// ERR_NUM_ERRORS is the limit of the number of errors in the queue. +// ERR_NUM_ERRORS is one more than the limit of the number of errors in the +// queue. #define ERR_NUM_ERRORS 16 -// err_state_st (aka |ERR_STATE|) contains the per-thread, error queue. -typedef struct err_state_st { - // errors contains the ERR_NUM_ERRORS most recent errors, organised as a ring - // buffer. - struct err_error_st errors[ERR_NUM_ERRORS]; - // top contains the index one past the most recent error. If |top| equals - // |bottom| then the queue is empty. - unsigned top; - // bottom contains the index of the last error in the queue. - unsigned bottom; - - // to_free, if not NULL, contains a pointer owned by this structure that was - // previously a |data| pointer of one of the elements of |errors|. - void *to_free; -} ERR_STATE; - #define ERR_PACK(lib, reason) \ (((((uint32_t)(lib)) & 0xff) << 24) | ((((uint32_t)(reason)) & 0xfff)))