Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions tests/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -22203,6 +22203,9 @@ static int test_wolfSSL_ERR_print_errors_cb(void)
"wolfSSL error occurred, error = 275 line:100 file:asn.c",
buf + 53, 55), 0);
ExpectIntEQ(BIO_gets(bio, buf, sizeof(buf)), 0);
/* queue and its read cursor were reset, nothing left to pull */
ExpectIntEQ(ERR_get_error_line(NULL, NULL), 0);
ExpectIntEQ(ERR_get_error(), 0);

BIO_free(bio);
#endif
Expand Down Expand Up @@ -22230,13 +22233,43 @@ static int test_WOLFSSL_ERROR_MSG(void)
*/
static int test_wc_ERR_remove_state(void)
{
int res = TEST_SKIPPED;
EXPECT_DECLS;
#if defined(OPENSSL_EXTRA) || defined(DEBUG_WOLFSSL_VERBOSE)
wc_ERR_remove_state();
ExpectIntEQ(wc_ERR_remove_state(), 0);

res = TEST_SUCCESS;
#if !defined(NO_ERROR_QUEUE) && defined(OPENSSL_EXTRA) && \
defined(DEBUG_WOLFSSL)
/* the read cursor must be reset with the nodes it points into */
ERR_clear_error();
ERR_put_error(0, SYS_F_ACCEPT, -173, "ssl.c", 0);
ERR_put_error(0, SYS_F_BIND, -275, "asn.c", 100);
ExpectIntEQ(wc_ERR_remove_state(), 0);
ExpectIntEQ(ERR_get_error_line(NULL, NULL), 0);
ExpectIntEQ(ERR_get_error(), 0);
#endif
return res;

#if defined(WOLFSSL_HAVE_ERROR_QUEUE) && !defined(NO_ERROR_QUEUE) && \
defined(OPENSSL_EXTRA) && defined(DEBUG_WOLFSSL) && \
!defined(ERROR_QUEUE_PER_THREAD)
{
/* with the global queue the returned file string must not point into
* the node, which is freed when the error is removed */
const char* file = NULL;
int line = 0;

ERR_clear_error();
ERR_put_error(0, SYS_F_BIND, -275, "asn.c", 100);
ExpectIntNE(ERR_peek_last_error_line(&file, &line), 0);
ExpectIntNE(ERR_get_error(), 0);
ExpectNotNull(file);
if (file != NULL) {
ExpectIntEQ(XSTRCMP(file, "asn.c"), 0);
}
ExpectIntEQ(line, 100);
}
#endif
#endif
return EXPECT_RESULT();
} /* End test_wc_ERR_remove_state */
/*
* Testing wc_ERR_print_errors_fp
Expand Down
53 changes: 24 additions & 29 deletions wolfcrypt/src/aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -9650,12 +9650,16 @@ static void GMULT(byte* X, byte* Y, GcmScratch* WC_RESTRICT ws)
byte y = Y[i];
for (j = 0; j < 8; j++)
{
if (y & 0x80) {
xorbuf(Z, V, WC_AES_BLOCK_SIZE);
byte mask;
int k;

mask = (byte)(0U - ((y >> 7) & 0x01U));
for (k = 0; k < WC_AES_BLOCK_SIZE; k++) {
Z[k] ^= (byte)(V[k] & mask);
}

RIGHTSHIFTX(V);
y = y << 1;
y = (byte)(y << 1);
}
}
XMEMCPY(X, Z, WC_AES_BLOCK_SIZE);
Expand Down Expand Up @@ -11023,31 +11027,18 @@ static void GMULT(word32* X, word32* Y, GcmScratch* WC_RESTRICT ws)
word32 y = Y[i];
for (j = 0; j < 32; j++)
{
if (y & 0x80000000) {
Z[0] ^= V[0];
Z[1] ^= V[1];
Z[2] ^= V[2];
Z[3] ^= V[3];
}
word32 mask = (word32)0U - (y >> 31);
word32 carry = (word32)0U - (V[3] & 0x00000001);

if (V[3] & 0x00000001) {
V[3] >>= 1;
V[3] |= ((V[2] & 0x00000001) ? 0x80000000 : 0);
V[2] >>= 1;
V[2] |= ((V[1] & 0x00000001) ? 0x80000000 : 0);
V[1] >>= 1;
V[1] |= ((V[0] & 0x00000001) ? 0x80000000 : 0);
V[0] >>= 1;
V[0] ^= 0xE1000000;
} else {
V[3] >>= 1;
V[3] |= ((V[2] & 0x00000001) ? 0x80000000 : 0);
V[2] >>= 1;
V[2] |= ((V[1] & 0x00000001) ? 0x80000000 : 0);
V[1] >>= 1;
V[1] |= ((V[0] & 0x00000001) ? 0x80000000 : 0);
V[0] >>= 1;
}
Z[0] ^= V[0] & mask;
Z[1] ^= V[1] & mask;
Z[2] ^= V[2] & mask;
Z[3] ^= V[3] & mask;

V[3] = (V[3] >> 1) | (V[2] << 31);
V[2] = (V[2] >> 1) | (V[1] << 31);
V[1] = (V[1] >> 1) | (V[0] << 31);
V[0] = (V[0] >> 1) ^ (0xE1000000 & carry);
y <<= 1;
}
}
Expand Down Expand Up @@ -21620,8 +21611,12 @@ static void AesGcmSivGMult(AesGcmSivPolyval* poly)
for (i = 0; i < WC_AES_BLOCK_SIZE; i++) {
byte y = poly->h[i];
for (j = 0; j < 8; j++) {
if (y & 0x80) {
xorbuf(Z, V, WC_AES_BLOCK_SIZE);
byte mask;
int k;

mask = (byte)(0U - ((y >> 7) & 0x01U));
for (k = 0; k < WC_AES_BLOCK_SIZE; k++) {
Z[k] ^= (byte)(V[k] & mask);
}
AesGcmSivMulX(V);
y = (byte)(y << 1);
Expand Down
1 change: 1 addition & 0 deletions wolfcrypt/src/des3.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ static WC_INLINE void wc_Stm32_CrypDesBlock(const byte* in, byte* out)
out += size;

}
ForceZero(secKey, secDesc->length3);
wc_UnLockMutex(&Mutex_DesSEC) ;

}
Expand Down
67 changes: 37 additions & 30 deletions wolfcrypt/src/logging.c
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,16 @@ static struct wc_error_queue* wc_current_node;
/* heap info currently used for allocation of entries */
static void* wc_error_heap;

/* Copies of the file and reason strings of the last node peeked or pulled.
* Pointers returned to the caller point here rather than into a node, so a
* remove or clear from another thread after the mutex is released can not
* leave the caller with a pointer to freed memory. The contents are only valid
* until the next peek or pull by the same thread. Only written with the mutex
* held and the last byte is never written, so the strings are always
* terminated. */
static THREAD_LS_T char wc_errq_file[WOLFSSL_MAX_ERROR_SZ];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error-queue string buffers are shared between threads when THREAD_LS_T is empty · Race conditions

In the mutex-protected global queue, wc_errq_file and wc_errq_reason become ordinary shared globals wherever THREAD_LS_T is empty: no HAVE_THREAD_LS, NO_THREAD_LS, FreeRTOS, Zephyr, or the kernel builds that #undef HAVE_THREAD_LS. They are written under the queue lock, but callers read them after ERRQ_UNLOCK(). Another thread's peek or pull can then overwrite the strings mid-read. Before this PR, peek and pull did not cause that.

Suggested fix: Fixing this for builds without working thread-local storage needs a design decision (caller-owned buffers, or requiring real TLS); see C11 §5.1.2.4 on data races.

Related known findings (similar but distinct; listed for context, not part of this finding)

  • F-9964: F-9964 is a use-after-free through pointers into heap nodes. This PR swaps that storage for buffers that are only thread-local when TLS works, so non-TLS multi-threaded builds now race on every concurrent peek or pull.

static THREAD_LS_T char wc_errq_reason[WOLFSSL_MAX_ERROR_SZ];

/* mutex for list operation protection */
static wolfSSL_Mutex wc_error_mutex WOLFSSL_MUTEX_INITIALIZER_CLAUSE(wc_error_mutex);
#define ERRQ_MUTEX_INIT() wc_InitMutex(&wc_error_mutex)
Expand Down Expand Up @@ -1109,6 +1119,21 @@ int wc_LoggingCleanup(void)
return 0;
}

/* Copy the node strings asked for to the static buffers and return those.
* Must be called with the mutex held. */
static void exportErrorNodeStrings(const struct wc_error_queue* err,
const char **file, const char **reason)
{
if (file != NULL) {
XMEMCPY(wc_errq_file, err->file, WOLFSSL_MAX_ERROR_SZ - 1);
*file = wc_errq_file;
}
if (reason != NULL) {
XMEMCPY(wc_errq_reason, err->error, WOLFSSL_MAX_ERROR_SZ - 1);
*reason = wc_errq_reason;
}
}

static int peekErrorNode(int idx, const char **file, const char **reason,
int *line)
{
Expand All @@ -1135,12 +1160,7 @@ static int peekErrorNode(int idx, const char **file, const char **reason,
return BAD_STATE_E;
}

if (file != NULL) {
*file = err->file;
}
if (reason != NULL) {
*reason = err->error;
}
exportErrorNodeStrings(err, file, reason);
if (line != NULL) {
*line = err->line;
}
Expand All @@ -1153,8 +1173,10 @@ static int peekErrorNode(int idx, const char **file, const char **reason,
* idx : if -1 then the most recent node is looked at,
* otherwise search through queue for node at the given index starting
* from the absolute head wc_errors
* file : pointer to internal file string
* reason : pointer to internal error reason
* file : pointer to a copy of the file string, valid until the next peek or
* pull
* reason : pointer to a copy of the error reason, valid until the next peek
* or pull
* line : line number that error happened at
*
* Returns a negative value in error case, on success returns the nodes error
Expand Down Expand Up @@ -1185,12 +1207,7 @@ static int pullErrorNode(const char **file, const char **reason, int *line)
return BAD_STATE_E;
}

if (file != NULL) {
*file = err->file;
}
if (reason != NULL) {
*reason = err->error;
}
exportErrorNodeStrings(err, file, reason);
if (line != NULL) {
*line = err->line;
}
Expand All @@ -1201,8 +1218,8 @@ static int pullErrorNode(const char **file, const char **reason, int *line)
}

/* Pulls the current node from error queue and increments current state.
* Note: this does not delete nodes because input arguments are pointing to
* node buffers.
* Note: this does not delete nodes. The file and reason pointers returned are
* to copies that are valid until the next peek or pull.
*
* file pointer to file that error was in. Can be NULL to return no file.
* reason error string giving reason for error. Can be NULL to return no reason.
Expand Down Expand Up @@ -1431,25 +1448,14 @@ int wc_SetLoggingHeap(void* h)
*/
int wc_ERR_remove_state(void)
{
struct wc_error_queue* current;
struct wc_error_queue* next;

if (ERRQ_LOCK() != 0) {
WOLFSSL_MSG("Lock debug mutex failed");
return BAD_MUTEX_E;
}

/* free all nodes from error queue */
current = (struct wc_error_queue*)wc_errors;
while (current != NULL) {
next = current->next;
XFREE(current, current->heap, DYNAMIC_TYPE_LOG);
current = next;
}

wc_errors_count = 0;
wc_errors = NULL;
wc_last_node = NULL;
/* free all nodes from error queue and reset all list pointers,
* including the wc_current_node cursor */
clearErrorNodes();

ERRQ_UNLOCK();

Expand Down Expand Up @@ -1584,6 +1590,7 @@ void wc_ERR_print_errors_cb(int (*cb)(const char *str, size_t len, void *u),
wc_errors_count = 0;
wc_errors = NULL;
wc_last_node = NULL;
wc_current_node = NULL;

ERRQ_UNLOCK();
}
Expand Down
Loading