| // Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // Ensure we can't call OPENSSL_malloc circularly. | |
| #define _BORINGSSL_PROHIBIT_OPENSSL_MALLOC | |
| #include <openssl/err.h> | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <limits.h> | |
| #include <stdarg.h> | |
| #include <string.h> | |
| #if defined(OPENSSL_WINDOWS) | |
| #include <windows.h> | |
| #endif | |
| #include <openssl/mem.h> | |
| #include "../internal.h" | |
| #include "./internal.h" | |
| using namespace bssl; | |
| namespace { | |
| 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 is allocated | |
| // with system `malloc` and must be freed with `free` (not `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 up to ERR_NUM_ERRORS - 1 most recent errors, organised as a | |
| // ring buffer. | |
| struct err_error_st errors[ERR_NUM_ERRORS]; | |
| // top contains the index of the most recent error. If `top` equals `bottom` | |
| // then the queue is empty. | |
| unsigned top; | |
| // bottom contains the index before the least recent 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; | |
| } // namespace | |
| BSSL_NAMESPACE_BEGIN | |
| extern const uint32_t kOpenSSLReasonValues[]; | |
| extern const size_t kOpenSSLReasonValuesLen; | |
| extern const char kOpenSSLReasonStringData[]; | |
| BSSL_NAMESPACE_END | |
| static char *strdup_libc_malloc(const char *str) { | |
| // `strdup` is not in C until C23, so MSVC triggers deprecation warnings, and | |
| // glibc and musl gate it on a feature macro. Reimplementing it is easier. | |
| size_t len = strlen(str); | |
| char *ret = reinterpret_cast<char *>(malloc(len + 1)); | |
| if (ret != nullptr) { | |
| memcpy(ret, str, len + 1); | |
| } | |
| return ret; | |
| } | |
| // err_clear clears the given queued error. | |
| static void err_clear(struct err_error_st *error) { | |
| free(error->data); | |
| 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 != nullptr) { | |
| // We can't use OPENSSL_strdup because we don't want to call OPENSSL_malloc, | |
| // which can affect the error stack. | |
| dst->data = strdup_libc_malloc(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; | |
| // global_next_library_mutex protects `global_next_library` from concurrent | |
| // updates. | |
| static StaticMutex global_next_library_mutex; | |
| static void err_state_free(void *statep) { | |
| ERR_STATE *state = reinterpret_cast<ERR_STATE *>(statep); | |
| if (state == nullptr) { | |
| return; | |
| } | |
| for (unsigned i = 0; i < ERR_NUM_ERRORS; i++) { | |
| err_clear(&state->errors[i]); | |
| } | |
| free(state->to_free); | |
| free(state); | |
| } | |
| // err_get_state gets the ERR_STATE object for the current thread. | |
| static ERR_STATE *err_get_state() { | |
| ERR_STATE *state = reinterpret_cast<ERR_STATE *>( | |
| CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_ERR)); | |
| if (state == nullptr) { | |
| state = reinterpret_cast<ERR_STATE *>(malloc(sizeof(ERR_STATE))); | |
| if (state == nullptr) { | |
| return nullptr; | |
| } | |
| OPENSSL_memset(state, 0, sizeof(ERR_STATE)); | |
| if (!CRYPTO_set_thread_local(OPENSSL_THREAD_LOCAL_ERR, state, | |
| err_state_free)) { | |
| return nullptr; | |
| } | |
| } | |
| return state; | |
| } | |
| static uint32_t get_error_values(int inc, int top, const char **file, int *line, | |
| const char **data, int *flags) { | |
| unsigned i = 0; | |
| ERR_STATE *state; | |
| struct err_error_st *error; | |
| uint32_t ret; | |
| state = err_get_state(); | |
| if (state == nullptr || state->bottom == state->top) { | |
| return 0; | |
| } | |
| if (top) { | |
| assert(!inc); | |
| // last error | |
| i = state->top; | |
| } else { | |
| i = (state->bottom + 1) % ERR_NUM_ERRORS; | |
| } | |
| error = &state->errors[i]; | |
| ret = error->packed; | |
| if (file != nullptr && line != nullptr) { | |
| if (error->file == nullptr) { | |
| *file = "NA"; | |
| *line = 0; | |
| } else { | |
| *file = error->file; | |
| *line = error->line; | |
| } | |
| } | |
| if (data != nullptr) { | |
| if (error->data == nullptr) { | |
| *data = ""; | |
| if (flags != nullptr) { | |
| *flags = 0; | |
| } | |
| } else { | |
| *data = error->data; | |
| if (flags != nullptr) { | |
| // Without `ERR_FLAG_MALLOCED`, rust-openssl assumes the string has a | |
| // static lifetime. In both cases, we retain ownership of the string, | |
| // and the caller is not expected to free it. | |
| *flags = ERR_FLAG_STRING | ERR_FLAG_MALLOCED; | |
| } | |
| // If this error is being removed, take ownership of data from | |
| // the error. The semantics are such that the caller doesn't | |
| // take ownership either. Instead the error system takes | |
| // ownership and retains it until the next call that affects the | |
| // error queue. | |
| if (inc) { | |
| if (error->data != nullptr) { | |
| free(state->to_free); | |
| state->to_free = error->data; | |
| } | |
| error->data = nullptr; | |
| } | |
| } | |
| } | |
| if (inc) { | |
| assert(!top); | |
| err_clear(error); | |
| state->bottom = i; | |
| } | |
| return ret; | |
| } | |
| uint32_t ERR_get_error() { | |
| return get_error_values(1 /* inc */, 0 /* bottom */, nullptr, nullptr, | |
| nullptr, nullptr); | |
| } | |
| uint32_t ERR_get_error_line(const char **file, int *line) { | |
| return get_error_values(1 /* inc */, 0 /* bottom */, file, line, nullptr, | |
| nullptr); | |
| } | |
| uint32_t ERR_get_error_line_data(const char **file, int *line, | |
| const char **data, int *flags) { | |
| return get_error_values(1 /* inc */, 0 /* bottom */, file, line, data, flags); | |
| } | |
| uint32_t ERR_peek_error() { | |
| return get_error_values(0 /* peek */, 0 /* bottom */, nullptr, nullptr, | |
| nullptr, nullptr); | |
| } | |
| uint32_t ERR_peek_error_line(const char **file, int *line) { | |
| return get_error_values(0 /* peek */, 0 /* bottom */, file, line, nullptr, | |
| nullptr); | |
| } | |
| uint32_t ERR_peek_error_line_data(const char **file, int *line, | |
| const char **data, int *flags) { | |
| return get_error_values(0 /* peek */, 0 /* bottom */, file, line, data, | |
| flags); | |
| } | |
| uint32_t ERR_peek_last_error() { | |
| return get_error_values(0 /* peek */, 1 /* top */, nullptr, nullptr, nullptr, | |
| nullptr); | |
| } | |
| uint32_t ERR_peek_last_error_line(const char **file, int *line) { | |
| return get_error_values(0 /* peek */, 1 /* top */, file, line, nullptr, | |
| nullptr); | |
| } | |
| uint32_t ERR_peek_last_error_line_data(const char **file, int *line, | |
| const char **data, int *flags) { | |
| return get_error_values(0 /* peek */, 1 /* top */, file, line, data, flags); | |
| } | |
| void ERR_clear_error() { | |
| ERR_STATE *const state = err_get_state(); | |
| unsigned i; | |
| if (state == nullptr) { | |
| return; | |
| } | |
| for (i = 0; i < ERR_NUM_ERRORS; i++) { | |
| err_clear(&state->errors[i]); | |
| } | |
| free(state->to_free); | |
| state->to_free = nullptr; | |
| state->top = state->bottom = 0; | |
| } | |
| void ERR_remove_thread_state(const CRYPTO_THREADID *tid) { | |
| if (tid != nullptr) { | |
| assert(0); | |
| return; | |
| } | |
| ERR_clear_error(); | |
| } | |
| int ERR_get_next_error_library() { | |
| MutexWriteLock lock(&global_next_library_mutex); | |
| return global_next_library++; | |
| } | |
| void ERR_remove_state(unsigned long pid) { ERR_clear_error(); } | |
| void ERR_clear_system_error() { errno = 0; } | |
| // 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) { | |
| const uint32_t a_key = *((const uint32_t *)a) >> 15; | |
| const uint32_t b_key = *((const uint32_t *)b) >> 15; | |
| if (a_key < b_key) { | |
| return -1; | |
| } else if (a_key > b_key) { | |
| return 1; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| // err_string_lookup looks up the string associated with `lib` and `key` in | |
| // `values` and `string_data`. It returns the string or NULL if not found. | |
| static const char *err_string_lookup(uint32_t lib, uint32_t key, | |
| const uint32_t *values, size_t num_values, | |
| const char *string_data) { | |
| // `values` points to data in err_data.h, which is generated by | |
| // err_data_generate.go. It's an array of uint32_t values. Each value has the | |
| // following structure: | |
| // | lib | key | offset | | |
| // |6 bits| 11 bits | 15 bits | | |
| // | |
| // The `lib` value is a library identifier: one of the `ERR_LIB_*` values. | |
| // The `key` is a reason code, depending on the context. | |
| // The `offset` is the number of bytes from the start of `string_data` where | |
| // the (NUL terminated) string for this value can be found. | |
| // | |
| // Values are sorted based on treating the `lib` and `key` part as an | |
| // unsigned integer. | |
| if (lib >= (1 << 6) || key >= (1 << 11)) { | |
| return nullptr; | |
| } | |
| uint32_t search_key = lib << 26 | key << 15; | |
| const uint32_t *result = reinterpret_cast<const uint32_t *>(bsearch( | |
| &search_key, values, num_values, sizeof(uint32_t), err_string_cmp)); | |
| if (result == nullptr) { | |
| return nullptr; | |
| } | |
| return &string_data[(*result) & 0x7fff]; | |
| } | |
| namespace { | |
| typedef struct library_name_st { | |
| const char *str; | |
| const char *symbol; | |
| const char *reason_symbol; | |
| } LIBRARY_NAME; | |
| } // namespace | |
| static const LIBRARY_NAME kLibraryNames[ERR_NUM_LIBS] = { | |
| {"invalid library (0)", nullptr, nullptr}, | |
| {"unknown library", "NONE", "NONE_LIB"}, | |
| {"system library", "SYS", "SYS_LIB"}, | |
| {"bignum routines", "BN", "BN_LIB"}, | |
| {"RSA routines", "RSA", "RSA_LIB"}, | |
| {"Diffie-Hellman routines", "DH", "DH_LIB"}, | |
| {"public key routines", "EVP", "EVP_LIB"}, | |
| {"memory buffer routines", "BUF", "BUF_LIB"}, | |
| {"object identifier routines", "OBJ", "OBJ_LIB"}, | |
| {"PEM routines", "PEM", "PEM_LIB"}, | |
| {"DSA routines", "DSA", "DSA_LIB"}, | |
| {"X.509 certificate routines", "X509", "X509_LIB"}, | |
| {"ASN.1 encoding routines", "ASN1", "ASN1_LIB"}, | |
| {"configuration file routines", "CONF", "CONF_LIB"}, | |
| {"common libcrypto routines", "CRYPTO", "CRYPTO_LIB"}, | |
| {"elliptic curve routines", "EC", "EC_LIB"}, | |
| {"SSL routines", "SSL", "SSL_LIB"}, | |
| {"BIO routines", "BIO", "BIO_LIB"}, | |
| {"PKCS7 routines", "PKCS7", "PKCS7_LIB"}, | |
| {"PKCS8 routines", "PKCS8", "PKCS8_LIB"}, | |
| {"X509 V3 routines", "X509V3", "X509V3_LIB"}, | |
| {"random number generator", "RAND", "RAND_LIB"}, | |
| {"ENGINE routines", "ENGINE", "ENGINE_LIB"}, | |
| {"OCSP routines", "OCSP", "OCSP_LIB"}, | |
| {"UI routines", "UI", "UI_LIB"}, | |
| {"COMP routines", "COMP", "COMP_LIB"}, | |
| {"ECDSA routines", "ECDSA", "ECDSA_LIB"}, | |
| {"ECDH routines", "ECDH", "ECDH_LIB"}, | |
| {"HMAC routines", "HMAC", "HMAC_LIB"}, | |
| {"Digest functions", "DIGEST", "DIGEST_LIB"}, | |
| {"Cipher functions", "CIPHER", "CIPHER_LIB"}, | |
| {"HKDF functions", "HKDF", "HKDF_LIB"}, | |
| {"Trust Token functions", "TRUST_TOKEN", "TRUST_TOKEN_LIB"}, | |
| {"CMS routines", "CMS", "CMS_LIB"}, | |
| {"User defined functions", "USER", "USER_LIB"}, | |
| }; | |
| static const char *err_lib_error_string(uint32_t packed_error) { | |
| const uint32_t lib = ERR_GET_LIB(packed_error); | |
| return lib >= ERR_NUM_LIBS ? nullptr : kLibraryNames[lib].str; | |
| } | |
| const char *ERR_lib_error_string(uint32_t packed_error) { | |
| const char *ret = err_lib_error_string(packed_error); | |
| return ret == nullptr ? "unknown library" : ret; | |
| } | |
| const char *ERR_lib_symbol_name(uint32_t packed_error) { | |
| const uint32_t lib = ERR_GET_LIB(packed_error); | |
| return lib >= ERR_NUM_LIBS ? nullptr : kLibraryNames[lib].symbol; | |
| } | |
| const char *ERR_func_error_string(uint32_t packed_error) { | |
| return "OPENSSL_internal"; | |
| } | |
| static const char *err_reason_error_string(uint32_t packed_error, int symbol) { | |
| const uint32_t lib = ERR_GET_LIB(packed_error); | |
| const uint32_t reason = ERR_GET_REASON(packed_error); | |
| if (lib == ERR_LIB_SYS) { | |
| if (!symbol && reason < 127) { | |
| return strerror(reason); | |
| } | |
| return nullptr; | |
| } | |
| if (reason < ERR_NUM_LIBS) { | |
| return symbol ? kLibraryNames[reason].reason_symbol | |
| : kLibraryNames[reason].str; | |
| } | |
| if (reason < 100) { | |
| // TODO(davidben): All our other reason strings match the symbol name. Only | |
| // the common ones differ. Should we just consistently return the symbol | |
| // name? | |
| switch (reason) { | |
| case ERR_R_MALLOC_FAILURE: | |
| return symbol ? "MALLOC_FAILURE" : "malloc failure"; | |
| case ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED: | |
| return symbol ? "SHOULD_NOT_HAVE_BEEN_CALLED" | |
| : "function should not have been called"; | |
| case ERR_R_PASSED_NULL_PARAMETER: | |
| return symbol ? "PASSED_NULL_PARAMETER" : "passed a null parameter"; | |
| case ERR_R_INTERNAL_ERROR: | |
| return symbol ? "INTERNAL_ERROR" : "internal error"; | |
| case ERR_R_OVERFLOW: | |
| return symbol ? "OVERFLOW" : "overflow"; | |
| default: | |
| return nullptr; | |
| } | |
| } | |
| // Unlike OpenSSL, BoringSSL's reason strings already match symbol name, so we | |
| // do not need to check `symbol`. | |
| return err_string_lookup(lib, reason, kOpenSSLReasonValues, | |
| kOpenSSLReasonValuesLen, kOpenSSLReasonStringData); | |
| } | |
| const char *ERR_reason_error_string(uint32_t packed_error) { | |
| const char *ret = err_reason_error_string(packed_error, /*symbol=*/0); | |
| return ret == nullptr ? "unknown error" : ret; | |
| } | |
| const char *ERR_reason_symbol_name(uint32_t packed_error) { | |
| return err_reason_error_string(packed_error, /*symbol=*/1); | |
| } | |
| char *ERR_error_string(uint32_t packed_error, char *ret) { | |
| static char buf[ERR_ERROR_STRING_BUF_LEN]; | |
| if (ret == nullptr) { | |
| // 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 nullptr; | |
| } | |
| 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, /*symbol=*/0); | |
| char lib_buf[32], reason_buf[32]; | |
| if (lib_str == nullptr) { | |
| snprintf(lib_buf, sizeof(lib_buf), "lib(%u)", lib); | |
| lib_str = lib_buf; | |
| } | |
| if (reason_str == nullptr) { | |
| snprintf(reason_buf, sizeof(reason_buf), "reason(%u)", reason); | |
| reason_str = reason_buf; | |
| } | |
| int ret = snprintf(buf, len, "error:%08" PRIx32 ":%s:OPENSSL_internal:%s", | |
| packed_error, lib_str, reason_str); | |
| if (ret >= 0 && (size_t)ret >= len) { | |
| // The output was 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 == nullptr || 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]; | |
| const char *file, *data; | |
| int line, flags; | |
| uint32_t packed_error; | |
| // thread_hash is the least-significant bits of the `ERR_STATE` pointer value | |
| // for this thread. | |
| const unsigned long thread_hash = (uintptr_t)err_get_state(); | |
| for (;;) { | |
| packed_error = ERR_get_error_line_data(&file, &line, &data, &flags); | |
| if (packed_error == 0) { | |
| break; | |
| } | |
| ERR_error_string_n(packed_error, buf, sizeof(buf)); | |
| snprintf(buf2, sizeof(buf2), "%lu:%s:%s:%d:%s\n", thread_hash, buf, file, | |
| line, (flags & ERR_FLAG_STRING) ? data : ""); | |
| if (callback(buf2, strlen(buf2), ctx) <= 0) { | |
| break; | |
| } | |
| } | |
| } | |
| static int print_errors_to_file(const char *msg, size_t msg_len, void *ctx) { | |
| assert(msg[msg_len] == '\0'); | |
| FILE *fp = reinterpret_cast<FILE *>(ctx); | |
| int res = fputs(msg, fp); | |
| return res < 0 ? 0 : 1; | |
| } | |
| void ERR_print_errors_fp(FILE *file) { | |
| ERR_print_errors_cb(print_errors_to_file, file); | |
| } | |
| // err_set_error_data sets the data on the most recent error. | |
| static void err_set_error_data(char *data) { | |
| ERR_STATE *const state = err_get_state(); | |
| struct err_error_st *error; | |
| if (state == nullptr || state->top == state->bottom) { | |
| free(data); | |
| return; | |
| } | |
| error = &state->errors[state->top]; | |
| free(error->data); | |
| error->data = data; | |
| } | |
| void ERR_put_error(int library, int unused, int reason, const char *file, | |
| unsigned line) { | |
| ERR_STATE *const state = err_get_state(); | |
| struct err_error_st *error; | |
| if (state == nullptr) { | |
| return; | |
| } | |
| if (library == ERR_LIB_SYS && reason == 0) { | |
| #if defined(OPENSSL_WINDOWS) | |
| reason = GetLastError(); | |
| #else | |
| reason = errno; | |
| #endif | |
| } | |
| state->top = (state->top + 1) % ERR_NUM_ERRORS; | |
| if (state->top == state->bottom) { | |
| state->bottom = (state->bottom + 1) % ERR_NUM_ERRORS; | |
| } | |
| error = &state->errors[state->top]; | |
| err_clear(error); | |
| error->file = file; | |
| error->line = line; | |
| error->packed = ERR_PACK(library, reason); | |
| } | |
| // ERR_add_error_data_vdata takes a variable number of const char* pointers, | |
| // concatenates them and sets the result as the data on the most recent | |
| // error. | |
| static void err_add_error_vdata(unsigned num, va_list args) { | |
| size_t total_size = 0; | |
| const char *substr; | |
| char *buf; | |
| va_list args_copy; | |
| va_copy(args_copy, args); | |
| for (size_t i = 0; i < num; i++) { | |
| substr = va_arg(args_copy, const char *); | |
| if (substr == nullptr) { | |
| continue; | |
| } | |
| size_t substr_len = strlen(substr); | |
| if (SIZE_MAX - total_size < substr_len) { | |
| return; // Would overflow. | |
| } | |
| total_size += substr_len; | |
| } | |
| va_end(args_copy); | |
| if (total_size == SIZE_MAX) { | |
| return; // Would overflow. | |
| } | |
| total_size += 1; // NUL terminator. | |
| if ((buf = reinterpret_cast<char *>(malloc(total_size))) == nullptr) { | |
| return; | |
| } | |
| buf[0] = '\0'; | |
| for (size_t i = 0; i < num; i++) { | |
| substr = va_arg(args, const char *); | |
| if (substr == nullptr) { | |
| continue; | |
| } | |
| if (OPENSSL_strlcat(buf, substr, total_size) >= total_size) { | |
| assert(0); // should not be possible. | |
| } | |
| } | |
| err_set_error_data(buf); | |
| } | |
| void ERR_add_error_data(unsigned count, ...) { | |
| va_list args; | |
| va_start(args, count); | |
| err_add_error_vdata(count, args); | |
| va_end(args); | |
| } | |
| void ERR_add_error_dataf(const char *format, ...) { | |
| char *buf = nullptr; | |
| va_list ap; | |
| va_start(ap, format); | |
| int len = OPENSSL_vasprintf_internal(&buf, format, ap, /*system_malloc=*/1); | |
| va_end(ap); | |
| if (len == -1) { | |
| return; | |
| } | |
| err_set_error_data(buf); | |
| } | |
| void ERR_set_error_data(char *data, int flags) { | |
| if (!(flags & ERR_FLAG_STRING)) { | |
| // We do not support non-string error data. | |
| assert(0); | |
| return; | |
| } | |
| // We can not use OPENSSL_strdup because we don't want to call OPENSSL_malloc, | |
| // which can affect the error stack. | |
| char *copy = strdup_libc_malloc(data); | |
| if (copy != nullptr) { | |
| err_set_error_data(copy); | |
| } | |
| if (flags & ERR_FLAG_MALLOCED) { | |
| // We can not take ownership of `data` directly because it is allocated with | |
| // `OPENSSL_malloc` and we will free it with system `free` later. | |
| OPENSSL_free(data); | |
| } | |
| } | |
| int ERR_set_mark() { | |
| ERR_STATE *const state = err_get_state(); | |
| if (state == nullptr || state->bottom == state->top) { | |
| return 0; | |
| } | |
| state->errors[state->top].mark = 1; | |
| return 1; | |
| } | |
| int ERR_pop_to_mark() { | |
| ERR_STATE *const state = err_get_state(); | |
| if (state == nullptr) { | |
| return 0; | |
| } | |
| while (state->bottom != state->top) { | |
| struct err_error_st *error = &state->errors[state->top]; | |
| if (error->mark) { | |
| error->mark = 0; | |
| return 1; | |
| } | |
| err_clear(error); | |
| if (state->top == 0) { | |
| state->top = ERR_NUM_ERRORS - 1; | |
| } else { | |
| state->top--; | |
| } | |
| } | |
| return 0; | |
| } | |
| void ERR_load_crypto_strings() {} | |
| void ERR_free_strings() {} | |
| void ERR_load_BIO_strings() {} | |
| void ERR_load_ERR_strings() {} | |
| void ERR_load_RAND_strings() {} | |
| BSSL_NAMESPACE_BEGIN | |
| struct err_save_state_st { | |
| struct err_error_st *errors; | |
| size_t num_errors; | |
| }; | |
| BSSL_NAMESPACE_END | |
| void bssl::ERR_SAVE_STATE_free(ERR_SAVE_STATE *state) { | |
| if (state == nullptr) { | |
| return; | |
| } | |
| for (size_t i = 0; i < state->num_errors; i++) { | |
| err_clear(&state->errors[i]); | |
| } | |
| free(state->errors); | |
| free(state); | |
| } | |
| ERR_SAVE_STATE *bssl::ERR_save_state() { | |
| ERR_STATE *const state = err_get_state(); | |
| if (state == nullptr || state->top == state->bottom) { | |
| return nullptr; | |
| } | |
| ERR_SAVE_STATE *ret = | |
| reinterpret_cast<ERR_SAVE_STATE *>(malloc(sizeof(ERR_SAVE_STATE))); | |
| if (ret == nullptr) { | |
| return nullptr; | |
| } | |
| // 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 = reinterpret_cast<err_error_st *>( | |
| malloc(num_errors * sizeof(struct err_error_st))); | |
| if (ret->errors == nullptr) { | |
| free(ret); | |
| return nullptr; | |
| } | |
| 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 bssl::ERR_restore_state(const ERR_SAVE_STATE *state) { | |
| if (state == nullptr || state->num_errors == 0) { | |
| ERR_clear_error(); | |
| return; | |
| } | |
| if (state->num_errors >= ERR_NUM_ERRORS) { | |
| abort(); | |
| } | |
| ERR_STATE *const dst = err_get_state(); | |
| if (dst == nullptr) { | |
| return; | |
| } | |
| for (size_t i = 0; i < state->num_errors; i++) { | |
| err_copy(&dst->errors[i], &state->errors[i]); | |
| } | |
| dst->top = (unsigned)(state->num_errors - 1); | |
| dst->bottom = ERR_NUM_ERRORS - 1; | |
| } |