This will be useful for the SSL stack to properly resurface handshake failures. Leave this in a private header and, along the way, hide the various types. (ERR_NUM_ERRORS didn't change in meaning. The old documentation was wrong.) Bug: 206 Change-Id: I4c6ca98d162d11ad5e17e4baf439a18fbe371018 Reviewed-on: https://boringssl-review.googlesource.com/21284 Commit-Queue: David Benjamin <davidben@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org> Reviewed-by: Steven Valdez <svaldez@google.com>kris/onging/CECPQ3_patch15
@@ -123,7 +123,38 @@ OPENSSL_MSVC_PRAGMA(warning(pop)) | |||
#include <openssl/thread.h> | |||
#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; | |||
} |
@@ -21,6 +21,8 @@ | |||
#include <openssl/err.h> | |||
#include <openssl/mem.h> | |||
#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<ERR_SAVE_STATE> 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()); | |||
} | |||
} |
@@ -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 <openssl/err.h> | |||
#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 |
@@ -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))) | |||