Skip to content

Commit

Permalink
ERR: special case system errors
Browse files Browse the repository at this point in the history
Because system errors can be any positive number that fits in an 'int'
according to POSIX, we can't reasonably expect them to be in the 1..127
range, even though that's the most usual.

Instead of packing them into the OpenSSL error code structure, we
recognise them as a special case and mark them as such by storing them
in our error queue with the highest bit set.  We make OpenSSL specific
error records have their highest bit cleared, and in doing so, we
shift down the library section of the code by one bit.  This still
leaves a very large section for the reason codes.

Of course, we must adapt the error code and reason string extraction
and printing functions accordingly.

With this, we also thrown away the pre-loaded array of system error
strings, and extract them from the system when needed instead, i.e.
when we create error strings.

Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
(Merged from #12343)
  • Loading branch information
levitte committed Jul 5, 2020
1 parent 163b2bc commit 71f2994
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 113 deletions.
104 changes: 22 additions & 82 deletions crypto/err/err.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,84 +173,6 @@ static ERR_STRING_DATA *int_err_get_item(const ERR_STRING_DATA *d)
return p;
}

#ifndef OPENSSL_NO_ERR
/* 2019-05-21: Russian and Ukrainian locales on Linux require more than 6,5 kB */
# define SPACE_SYS_STR_REASONS 8 * 1024
# define NUM_SYS_STR_REASONS 127

static ERR_STRING_DATA SYS_str_reasons[NUM_SYS_STR_REASONS + 1];
/*
* SYS_str_reasons is filled with copies of strerror() results at
* initialization. 'errno' values up to 127 should cover all usual errors,
* others will be displayed numerically by ERR_error_string. It is crucial
* that we have something for each reason code that occurs in
* ERR_str_reasons, or bogus reason strings will be returned for SYSerr(),
* which always gets an errno value and never one of those 'standard' reason
* codes.
*/

static void build_SYS_str_reasons(void)
{
/* OPENSSL_malloc cannot be used here, use static storage instead */
static char strerror_pool[SPACE_SYS_STR_REASONS];
char *cur = strerror_pool;
size_t cnt = 0;
static int init = 1;
int i;
int saveerrno = get_last_sys_error();

CRYPTO_THREAD_write_lock(err_string_lock);
if (!init) {
CRYPTO_THREAD_unlock(err_string_lock);
return;
}

for (i = 1; i <= NUM_SYS_STR_REASONS; i++) {
ERR_STRING_DATA *str = &SYS_str_reasons[i - 1];

str->error = ERR_PACK(ERR_LIB_SYS, 0, i);
/*
* If we have used up all the space in strerror_pool,
* there's no point in calling openssl_strerror_r()
*/
if (str->string == NULL && cnt < sizeof(strerror_pool)) {
if (openssl_strerror_r(i, cur, sizeof(strerror_pool) - cnt)) {
size_t l = strlen(cur);

str->string = cur;
cnt += l;
cur += l;

/*
* VMS has an unusual quirk of adding spaces at the end of
* some (most? all?) messages. Lets trim them off.
*/
while (cur > strerror_pool && ossl_isspace(cur[-1])) {
cur--;
cnt--;
}
*cur++ = '\0';
cnt++;
}
}
if (str->string == NULL)
str->string = "unknown";
}

/*
* Now we still have SYS_str_reasons[NUM_SYS_STR_REASONS] = {0, NULL}, as
* required by ERR_load_strings.
*/

init = 0;

CRYPTO_THREAD_unlock(err_string_lock);
/* openssl_strerror_r could change errno, but we want to preserve it */
set_sys_error(saveerrno);
err_load_strings(SYS_str_reasons);
}
#endif

static void ERR_STATE_free(ERR_STATE *s)
{
int i;
Expand Down Expand Up @@ -322,7 +244,6 @@ int ERR_load_ERR_strings(void)

err_load_strings(ERR_str_libraries);
err_load_strings(ERR_str_reasons);
build_SYS_str_reasons();
#endif
return 1;
}
Expand Down Expand Up @@ -569,8 +490,8 @@ static unsigned long get_error_values(ERR_GET_ACTION g,

void ERR_error_string_n(unsigned long e, char *buf, size_t len)
{
char lsbuf[64], rsbuf[64];
const char *ls, *rs;
char lsbuf[64], rsbuf[256];
const char *ls, *rs = NULL;
unsigned long f = 0, l, r;

if (len == 0)
Expand All @@ -583,8 +504,19 @@ void ERR_error_string_n(unsigned long e, char *buf, size_t len)
ls = lsbuf;
}

rs = ERR_reason_error_string(e);
/*
* ERR_reason_error_string() can't safely return system error strings,
* since it would call openssl_strerror_r(), which needs a buffer for
* thread safety. So for system errors, we call openssl_strerror_r()
* directly instead.
*/
r = ERR_GET_REASON(e);
if (ERR_SYSTEM_ERROR(e)) {
if (openssl_strerror_r(r, rsbuf, sizeof(rsbuf)))
rs = rsbuf;
} else {
rs = ERR_reason_error_string(e);
}
if (rs == NULL) {
BIO_snprintf(rsbuf, sizeof(rsbuf), "reason(%lu)", r);
rs = rsbuf;
Expand Down Expand Up @@ -642,6 +574,14 @@ const char *ERR_reason_error_string(unsigned long e)
return NULL;
}

/*
* ERR_reason_error_string() can't safely return system error strings,
* since openssl_strerror_r() needs a buffer for thread safety, and we
* haven't got one that would serve any sensible purpose.
*/
if (ERR_SYSTEM_ERROR(e))
return NULL;

l = ERR_GET_LIB(e);
r = ERR_GET_REASON(e);
d.error = ERR_PACK(l, 0, r);
Expand Down
5 changes: 4 additions & 1 deletion crypto/err/err_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ static ossl_inline void err_clear_data(ERR_STATE *es, size_t i, int deall)
static ossl_inline void err_set_error(ERR_STATE *es, size_t i,
int lib, int reason)
{
es->err_buffer[i] = ERR_PACK(lib, 0, reason);
es->err_buffer[i] =
lib == ERR_LIB_SYS
? (unsigned int)(ERR_SYSTEM_FLAG | reason)
: ERR_PACK(lib, 0, reason);
}

static ossl_inline void err_set_debug(ERR_STATE *es, size_t i,
Expand Down
26 changes: 23 additions & 3 deletions crypto/err/err_prn.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,36 @@ void ERR_print_errors_cb(int (*cb) (const char *str, size_t len, void *u),
{
CRYPTO_THREAD_ID tid = CRYPTO_THREAD_get_current_id();
unsigned long l;
char buf[ERR_PRINT_BUF_SIZE], *hex;
const char *lib, *reason;
const char *file, *data, *func;
int line, flags;

while ((l = ERR_get_error_all(&file, &line, &func, &data, &flags)) != 0) {
char buf[ERR_PRINT_BUF_SIZE], *hex;
const char *lib, *reason = NULL;
char rsbuf[256];
unsigned long r = ERR_GET_REASON(l);

lib = ERR_lib_error_string(l);
reason = ERR_reason_error_string(l);

/*
* ERR_reason_error_string() can't safely return system error strings,
* since it would call openssl_strerror_r(), which needs a buffer for
* thread safety. So for system errors, we call openssl_strerror_r()
* directly instead.
*/
if (ERR_SYSTEM_ERROR(l)) {
if (openssl_strerror_r(r, rsbuf, sizeof(rsbuf)))
reason = rsbuf;
} else {
reason = ERR_reason_error_string(l);
}

if (func == NULL)
func = "unknown function";
if (reason == NULL) {
BIO_snprintf(rsbuf, sizeof(rsbuf), "reason(%lu)", r);
reason = rsbuf;
}
if ((flags & ERR_TXT_STRING) == 0)
data = "";
hex = openssl_buf2hexstr_sep((const unsigned char *)&tid, sizeof(tid),
Expand Down
107 changes: 80 additions & 27 deletions include/openssl/err.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ extern "C" {
# endif
# endif

# include <limits.h>
# include <errno.h>

# define ERR_TXT_MALLOCED 0x01
Expand Down Expand Up @@ -163,43 +164,95 @@ struct err_state_st {
# define X509err(f, r) ERR_raise_data(ERR_LIB_X509, (r), NULL)
# endif

/*
* The error code currently packs as follows (viewed as hex nibbles):
/*-
* The error code packs differently depending on if it records a system
* error or an OpenSSL error.
*
* LL rRRRRR
* A system error packs like this (we follow POSIX and only allow positive
* numbers that fit in an |int|):
*
* Where LL is the library code, r is the reason flags, and rRRRRR is the
* reason code.
* Do note that the reason flags is part of the reason code, and could as
* well be seen as a section of all possible reason codes. We do this for
* backward compatibility reasons, i.e. how ERR_R_FATAL was implemented.
* +-+-------------------------------------------------------------+
* |1| system error number |
* +-+-------------------------------------------------------------+
*
* An OpenSSL error packs like this:
*
* System errors (ERR_LIB_SYS) are structured the same way, except they
* don't have any reason flag.
* <---------------------------- 32 bits -------------------------->
* <--- 8 bits ---><------------------ 23 bits ----------------->
* +-+---------------+---------------------------------------------+
* |0| library | reason |
* +-+---------------+---------------------------------------------+
*
* LL RRRRRR
* A few of the reason bits are reserved as flags with special meaning:
*
* <4 bits><-------------- 19 bits ------------->
* +-------+-------------------------------------+
* | rflags| reason |
* +-------+-------------------------------------+
*
* We have the reason flags being part of the overall reason code for
* backward compatibility reasons, i.e. how ERR_R_FATAL was implemented.
*/
# define ERR_LIB_OFFSET 24L
# define ERR_LIB_MASK 0xFF
# define ERR_RFLAGS_OFFSET 20L
# define ERR_RFLAGS_MASK 0xF
# define ERR_REASON_MASK 0XFFFFFF

/* Macros to help decode recorded system errors */
# define ERR_SYSTEM_FLAG ((unsigned int)INT_MAX + 1)
# define ERR_SYSTEM_MASK ((unsigned int)INT_MAX)

/* Macros to help decode recorded OpenSSL errors */
# define ERR_LIB_OFFSET 23L
# define ERR_LIB_MASK 0xFF
# define ERR_RFLAGS_OFFSET 19L
# define ERR_RFLAGS_MASK 0xF
# define ERR_REASON_MASK 0X7FFFFF

/*
* Reason flags are defined pre-shifted to easily combine with the reason
* number.
*/
# define ERR_RFLAG_FATAL (0x1 << ERR_RFLAGS_OFFSET)

/* ERR_PACK takes reason flags and reason code combined in |r| */
# define ERR_PACK(l,f,r) \
( (((unsigned int)(l) & ERR_LIB_MASK) << ERR_LIB_OFFSET) | \
(((unsigned int)(r) & ERR_REASON_MASK)) )
# define ERR_GET_LIB(l) (int)(((l) >> ERR_LIB_OFFSET) & ERR_LIB_MASK)
# define ERR_GET_FUNC(l) 0
# define ERR_GET_RFLAGS(l) (int)((l) & (ERR_RFLAGS_MASK << ERR_RFLAGS_OFFSET))
# define ERR_GET_REASON(l) (int)((l) & ERR_REASON_MASK)
# define ERR_FATAL_ERROR(l) (int)((l) & ERR_RFLAG_FATAL)
# define ERR_RFLAG_FATAL (0x1 << ERR_RFLAGS_OFFSET)

# define ERR_SYSTEM_ERROR(errcode) (((errcode) & ERR_SYSTEM_FLAG) != 0)

static ossl_inline int ERR_GET_LIB(unsigned long errcode)
{
if (ERR_SYSTEM_ERROR(errcode))
return ERR_LIB_SYS;
return (errcode >> ERR_LIB_OFFSET) & ERR_LIB_MASK;
}

static ossl_inline int ERR_GET_FUNC(unsigned long errcode)
{
return 0;
}

static ossl_inline int ERR_GET_RFLAGS(unsigned long errcode)
{
if (ERR_SYSTEM_ERROR(errcode))
return 0;
return errcode & (ERR_RFLAGS_MASK << ERR_RFLAGS_OFFSET);
}

static ossl_inline int ERR_GET_REASON(unsigned long errcode)
{
if (ERR_SYSTEM_ERROR(errcode))
return errcode & ERR_SYSTEM_MASK;
return errcode & ERR_REASON_MASK;
}

static ossl_inline int ERR_FATAL_ERROR(unsigned long errcode)
{
return (ERR_GET_RFLAGS(errcode) & ERR_RFLAG_FATAL) != 0;
}

/*
* ERR_PACK is a helper macro to properly pack OpenSSL error codes and may
* only be used for that purpose. System errors are packed internally.
* ERR_PACK takes reason flags and reason code combined in |reason|.
* ERR_PACK ignores |func|, that parameter is just legacy from pre-3.0 OpenSSL.
*/
# define ERR_PACK(lib,func,reason) \
( (((unsigned long)(lib) & ERR_LIB_MASK ) << ERR_LIB_OFFSET) | \
(((unsigned long)(reason) & ERR_REASON_MASK)) )

# ifndef OPENSSL_NO_DEPRECATED_3_0
# define SYS_F_FOPEN 0
Expand Down

0 comments on commit 71f2994

Please sign in to comment.