mirror of
https://github.com/postgres/postgres.git
synced 2025-10-18 04:29:09 +03:00
Various code paths of the ECPG code did not check for memory allocation failures, including the specific case where ecpg_strdup() considers a NULL value given in input as a valid behavior. strdup() returning itself NULL on failure, there was no way to make the difference between what could be valid and what should fail. With the different cases in mind, ecpg_strdup() is redesigned and gains a new optional argument, giving its callers the possibility to differentiate allocation failures and valid cases where the caller is giving a NULL value in input. Most of the ECPG code does not expect a NULL value, at the exception of ECPGget_desc() (setlocale) and ECPGconnect(), like dbname being unspecified, with repeated strdup calls. The code is adapted to work with this new routine. Note the case of ecpg_auto_prepare(), where the code order is switched so as we handle failures with ecpg_strdup() before manipulating any cached data, avoiding inconsistencies. This class of failure is unlikely a problem in practice, so no backpatch is done. Random OOM failures in ECPGconnect() could cause the driver to connect to a different server than the one wanted by the caller, because it could fallback to default values instead of the parameters defined depending on the combinations of allocation failures and successes. Author: Evgeniy Gorbanev <gorbanyoves@basealt.ru> Co-authored-by: Aleksander Alekseev <aleksander@tigerdata.com> Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/a6b193c1-6994-4d9c-9059-aca4aaf41ddd@basealt.ru
177 lines
3.0 KiB
C
177 lines
3.0 KiB
C
/* src/interfaces/ecpg/ecpglib/memory.c */
|
|
|
|
#define POSTGRES_ECPG_INTERNAL
|
|
#include "postgres_fe.h"
|
|
|
|
#include "ecpg-pthread-win32.h"
|
|
#include "ecpgerrno.h"
|
|
#include "ecpglib.h"
|
|
#include "ecpglib_extern.h"
|
|
#include "ecpgtype.h"
|
|
|
|
void
|
|
ecpg_free(void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
|
|
char *
|
|
ecpg_alloc(long size, int lineno)
|
|
{
|
|
char *new = (char *) calloc(1L, size);
|
|
|
|
if (!new)
|
|
{
|
|
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
return new;
|
|
}
|
|
|
|
char *
|
|
ecpg_realloc(void *ptr, long size, int lineno)
|
|
{
|
|
char *new = (char *) realloc(ptr, size);
|
|
|
|
if (!new)
|
|
{
|
|
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
return new;
|
|
}
|
|
|
|
/*
|
|
* Wrapper for strdup(), with NULL in input treated as a correct case.
|
|
*
|
|
* "alloc_failed" can be optionally specified by the caller to check for
|
|
* allocation failures. The caller is responsible for its initialization,
|
|
* as ecpg_strdup() may be called repeatedly across multiple allocations.
|
|
*/
|
|
char *
|
|
ecpg_strdup(const char *string, int lineno, bool *alloc_failed)
|
|
{
|
|
char *new;
|
|
|
|
if (string == NULL)
|
|
return NULL;
|
|
|
|
new = strdup(string);
|
|
if (!new)
|
|
{
|
|
if (alloc_failed)
|
|
*alloc_failed = true;
|
|
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
return new;
|
|
}
|
|
|
|
/* keep a list of memory we allocated for the user */
|
|
struct auto_mem
|
|
{
|
|
void *pointer;
|
|
struct auto_mem *next;
|
|
};
|
|
|
|
static pthread_key_t auto_mem_key;
|
|
static pthread_once_t auto_mem_once = PTHREAD_ONCE_INIT;
|
|
|
|
static void
|
|
auto_mem_destructor(void *arg)
|
|
{
|
|
(void) arg; /* keep the compiler quiet */
|
|
ECPGfree_auto_mem();
|
|
}
|
|
|
|
static void
|
|
auto_mem_key_init(void)
|
|
{
|
|
pthread_key_create(&auto_mem_key, auto_mem_destructor);
|
|
}
|
|
|
|
static struct auto_mem *
|
|
get_auto_allocs(void)
|
|
{
|
|
pthread_once(&auto_mem_once, auto_mem_key_init);
|
|
return (struct auto_mem *) pthread_getspecific(auto_mem_key);
|
|
}
|
|
|
|
static void
|
|
set_auto_allocs(struct auto_mem *am)
|
|
{
|
|
pthread_setspecific(auto_mem_key, am);
|
|
}
|
|
|
|
char *
|
|
ecpg_auto_alloc(long size, int lineno)
|
|
{
|
|
void *ptr = ecpg_alloc(size, lineno);
|
|
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
if (!ecpg_add_mem(ptr, lineno))
|
|
{
|
|
ecpg_free(ptr);
|
|
return NULL;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
bool
|
|
ecpg_add_mem(void *ptr, int lineno)
|
|
{
|
|
struct auto_mem *am = (struct auto_mem *) ecpg_alloc(sizeof(struct auto_mem), lineno);
|
|
|
|
if (!am)
|
|
return false;
|
|
|
|
am->pointer = ptr;
|
|
am->next = get_auto_allocs();
|
|
set_auto_allocs(am);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ECPGfree_auto_mem(void)
|
|
{
|
|
struct auto_mem *am = get_auto_allocs();
|
|
|
|
/* free all memory we have allocated for the user */
|
|
if (am)
|
|
{
|
|
do
|
|
{
|
|
struct auto_mem *act = am;
|
|
|
|
am = am->next;
|
|
ecpg_free(act->pointer);
|
|
ecpg_free(act);
|
|
} while (am);
|
|
set_auto_allocs(NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
ecpg_clear_auto_mem(void)
|
|
{
|
|
struct auto_mem *am = get_auto_allocs();
|
|
|
|
/* only free our own structure */
|
|
if (am)
|
|
{
|
|
do
|
|
{
|
|
struct auto_mem *act = am;
|
|
|
|
am = am->next;
|
|
ecpg_free(act);
|
|
} while (am);
|
|
set_auto_allocs(NULL);
|
|
}
|
|
}
|