mirror of https://github.com/citusdata/citus.git
298 lines
7.9 KiB
C
298 lines
7.9 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* safe_lib.c
|
|
*
|
|
* This file contains all SafeXXXX helper functions that we implement to
|
|
* replace missing xxxx_s functions implemented by safestringlib. It also
|
|
* contains a constraint handler for use in both our SafeXXX and safestringlib
|
|
* its xxxx_s functions.
|
|
*
|
|
* Copyright (c) Citus Data, Inc.
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "distributed/pg_version_constants.h"
|
|
|
|
#include "safe_lib.h"
|
|
|
|
#include <limits.h>
|
|
|
|
#include "distributed/citus_safe_lib.h"
|
|
#include "lib/stringinfo.h"
|
|
|
|
#define citus_vsnprintf pg_vsnprintf
|
|
|
|
|
|
/*
|
|
* ereport_constraint_handler is a constraint handler that calls ereport. A
|
|
* constraint handler is called whenever an error occurs in any of the
|
|
* safestringlib xxxx_s functions or our SafeXXXX functions.
|
|
*
|
|
* More info on constraint handlers can be found here:
|
|
* https://en.cppreference.com/w/c/error/set_constraint_handler_s
|
|
*/
|
|
void
|
|
ereport_constraint_handler(const char *message,
|
|
void *pointer,
|
|
errno_t error)
|
|
{
|
|
if (message && error)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg(
|
|
"Memory constraint error: %s (errno %d)", message, error)));
|
|
}
|
|
else if (message)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg(
|
|
"Memory constraint error: %s", message)));
|
|
}
|
|
else if (error)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg(
|
|
"Unknown function failed with memory constraint error (errno %d)",
|
|
error)));
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg(
|
|
"Unknown function failed with memory constraint error")));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* SafeStringToInt64 converts a string containing a number to a int64. When it
|
|
* fails it calls ereport.
|
|
*
|
|
* The different error cases are inspired by
|
|
* https://stackoverflow.com/a/26083517/2570866
|
|
*/
|
|
int64
|
|
SafeStringToInt64(const char *str)
|
|
{
|
|
char *endptr;
|
|
errno = 0;
|
|
long long number = strtoll(str, &endptr, 10);
|
|
|
|
if (str == endptr)
|
|
{
|
|
ereport(ERROR, (errmsg("Error parsing %s as int64, no digits found\n", str)));
|
|
}
|
|
else if ((errno == ERANGE && number == LLONG_MIN) || number < INT64_MIN)
|
|
{
|
|
ereport(ERROR, (errmsg("Error parsing %s as int64, underflow occurred\n", str)));
|
|
}
|
|
else if ((errno == ERANGE && number == LLONG_MAX) || number > INT64_MAX)
|
|
{
|
|
ereport(ERROR, (errmsg("Error parsing %s as int64, overflow occurred\n", str)));
|
|
}
|
|
else if (errno == EINVAL)
|
|
{
|
|
ereport(ERROR, (errmsg(
|
|
"Error parsing %s as int64, base contains unsupported value\n",
|
|
str)));
|
|
}
|
|
else if (errno != 0 && number == 0)
|
|
{
|
|
int err = errno;
|
|
ereport(ERROR, (errmsg("Error parsing %s as int64, errno %d\n", str, err)));
|
|
}
|
|
else if (errno == 0 && str && *endptr != '\0')
|
|
{
|
|
ereport(ERROR, (errmsg(
|
|
"Error parsing %s as int64, aditional characters remain after int64\n",
|
|
str)));
|
|
}
|
|
return number;
|
|
}
|
|
|
|
|
|
/*
|
|
* SafeStringToUint64 converts a string containing a number to a uint64. When it
|
|
* fails it calls ereport.
|
|
*
|
|
* The different error cases are inspired by
|
|
* https://stackoverflow.com/a/26083517/2570866
|
|
*/
|
|
uint64
|
|
SafeStringToUint64(const char *str)
|
|
{
|
|
char *endptr;
|
|
errno = 0;
|
|
unsigned long long number = strtoull(str, &endptr, 10);
|
|
|
|
if (str == endptr)
|
|
{
|
|
ereport(ERROR, (errmsg("Error parsing %s as uint64, no digits found\n", str)));
|
|
}
|
|
else if ((errno == ERANGE && number == ULLONG_MAX) || number > UINT64_MAX)
|
|
{
|
|
ereport(ERROR, (errmsg("Error parsing %s as uint64, overflow occurred\n", str)));
|
|
}
|
|
else if (errno == EINVAL)
|
|
{
|
|
ereport(ERROR, (errmsg(
|
|
"Error parsing %s as uint64, base contains unsupported value\n",
|
|
str)));
|
|
}
|
|
else if (errno != 0 && number == 0)
|
|
{
|
|
int err = errno;
|
|
ereport(ERROR, (errmsg("Error parsing %s as uint64, errno %d\n", str, err)));
|
|
}
|
|
else if (errno == 0 && str && *endptr != '\0')
|
|
{
|
|
ereport(ERROR, (errmsg(
|
|
"Error parsing %s as uint64, aditional characters remain after uint64\n",
|
|
str)));
|
|
}
|
|
return number;
|
|
}
|
|
|
|
|
|
/*
|
|
* SafeQsort is the non reentrant version of qsort (qsort vs qsort_r), but it
|
|
* does the input checks required for qsort_s:
|
|
* 1. count or size is greater than RSIZE_MAX
|
|
* 2. ptr or comp is a null pointer (unless count is zero)
|
|
* source: https://en.cppreference.com/w/c/algorithm/qsort
|
|
*
|
|
* When it hits these errors it calls the ereport_constraint_handler.
|
|
*
|
|
* NOTE: this functions calls pg_qsort instead of stdlib qsort.
|
|
*/
|
|
void
|
|
SafeQsort(void *ptr, rsize_t count, rsize_t size,
|
|
int (*comp)(const void *, const void *))
|
|
{
|
|
if (count > RSIZE_MAX_MEM)
|
|
{
|
|
ereport_constraint_handler("SafeQsort: count exceeds max",
|
|
NULL, ESLEMAX);
|
|
}
|
|
|
|
if (size > RSIZE_MAX_MEM)
|
|
{
|
|
ereport_constraint_handler("SafeQsort: size exceeds max",
|
|
NULL, ESLEMAX);
|
|
}
|
|
if (size != 0)
|
|
{
|
|
if (ptr == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeQsort: ptr is NULL", NULL, ESNULLP);
|
|
}
|
|
if (comp == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeQsort: comp is NULL", NULL, ESNULLP);
|
|
}
|
|
}
|
|
pg_qsort(ptr, count, size, comp);
|
|
}
|
|
|
|
|
|
/*
|
|
* SafeBsearch is a non reentrant version of bsearch, but it does the
|
|
* input checks required for bsearch_s:
|
|
* 1. count or size is greater than RSIZE_MAX
|
|
* 2. key, ptr or comp is a null pointer (unless count is zero)
|
|
* source: https://en.cppreference.com/w/c/algorithm/bsearch
|
|
*
|
|
* When it hits these errors it calls the ereport_constraint_handler.
|
|
*
|
|
* NOTE: this functions calls pg_qsort instead of stdlib qsort.
|
|
*/
|
|
void *
|
|
SafeBsearch(const void *key, const void *ptr, rsize_t count, rsize_t size,
|
|
int (*comp)(const void *, const void *))
|
|
{
|
|
if (count > RSIZE_MAX_MEM)
|
|
{
|
|
ereport_constraint_handler("SafeBsearch: count exceeds max",
|
|
NULL, ESLEMAX);
|
|
}
|
|
|
|
if (size > RSIZE_MAX_MEM)
|
|
{
|
|
ereport_constraint_handler("SafeBsearch: size exceeds max",
|
|
NULL, ESLEMAX);
|
|
}
|
|
if (size != 0)
|
|
{
|
|
if (key == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeBsearch: key is NULL", NULL, ESNULLP);
|
|
}
|
|
if (ptr == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeBsearch: ptr is NULL", NULL, ESNULLP);
|
|
}
|
|
if (comp == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeBsearch: comp is NULL", NULL, ESNULLP);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Explanation of IGNORE-BANNED:
|
|
* bsearch is safe to use here since we check the same thing bsearch_s
|
|
* does. We cannot use bsearch_s as a replacement, since it's not available
|
|
* in safestringlib.
|
|
*/
|
|
return bsearch(key, ptr, count, size, comp); /* IGNORE-BANNED */
|
|
}
|
|
|
|
|
|
/*
|
|
* SafeSnprintf is a safer replacement for snprintf, which is needed since
|
|
* safestringlib doesn't implement snprintf_s.
|
|
*
|
|
* The required failure modes of snprint_s are as follows (in parentheses if
|
|
* this implements it and how):
|
|
* 1. the conversion specifier %n is present in format (yes, %n is not
|
|
* supported by pg_vsnprintf)
|
|
* 2. any of the arguments corresponding to %s is a null pointer (half, checked
|
|
* in postgres when asserts are enabled)
|
|
* 3. format or buffer is a null pointer (yes, checked by this function)
|
|
* 4. bufsz is zero or greater than RSIZE_MAX (yes, checked by this function)
|
|
* 5. encoding errors occur in any of string and character conversion
|
|
* specifiers (no clue what postgres does in this case)
|
|
* source: https://en.cppreference.com/w/c/io/fprintf
|
|
*/
|
|
int
|
|
SafeSnprintf(char *restrict buffer, rsize_t bufsz, const char *restrict format, ...)
|
|
{
|
|
/* failure mode 3 */
|
|
if (buffer == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeSnprintf: buffer is NULL", NULL, ESNULLP);
|
|
}
|
|
if (format == NULL)
|
|
{
|
|
ereport_constraint_handler("SafeSnprintf: format is NULL", NULL, ESNULLP);
|
|
}
|
|
|
|
/* failure mode 4 */
|
|
if (bufsz == 0)
|
|
{
|
|
ereport_constraint_handler("SafeSnprintf: bufsz is 0",
|
|
NULL, ESZEROL);
|
|
}
|
|
|
|
if (bufsz > RSIZE_MAX_STR)
|
|
{
|
|
ereport_constraint_handler("SafeSnprintf: bufsz exceeds max",
|
|
NULL, ESLEMAX);
|
|
}
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
size_t result = citus_vsnprintf(buffer, bufsz, format, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|