Add missing connection validation in ECPG

ECPGdeallocate_all(), ECPGprepared_statement(), ECPGget_desc(), and
ecpg_freeStmtCacheEntry() could crash with a SIGSEGV when called
without an established connection (for example, when EXEC SQL CONNECT
was forgotten or a non-existent connection name was used), because
they dereferenced the result of ecpg_get_connection() without first
checking it for NULL.

Each site is fixed in the style of the surrounding code.

New tests are added for these conditions.

Author: Shruthi Gowda <gowdashru@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Mahendra Singh Thalor <mahi6run@gmail.com>
Reviewed-by: Nishant Sharma <nishant.sharma@enterprisedb.com>
Discussion: https://postgr.es/m/3007317.1765210195@sss.pgh.pa.us
Backpatch-through: 14
REL_15_STABLE
Andrew Dunstan 6 days ago
parent e4f035de45
commit 6916f44100
  1. 12
      src/interfaces/ecpg/ecpglib/descriptor.c
  2. 32
      src/interfaces/ecpg/ecpglib/prepare.c
  3. 2
      src/interfaces/ecpg/test/connect/.gitignore
  4. 3
      src/interfaces/ecpg/test/connect/Makefile
  5. 68
      src/interfaces/ecpg/test/connect/test6.pgc
  6. 1
      src/interfaces/ecpg/test/ecpg_schedule
  7. 146
      src/interfaces/ecpg/test/expected/connect-test6.c
  8. 50
      src/interfaces/ecpg/test/expected/connect-test6.stderr
  9. 9
      src/interfaces/ecpg/test/expected/connect-test6.stdout

@ -482,6 +482,16 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
memset(&stmt, 0, sizeof stmt);
stmt.lineno = lineno;
/* desperate try to guess something sensible */
stmt.connection = ecpg_get_connection(NULL);
if (stmt.connection == NULL)
{
ecpg_raise(lineno, ECPG_NO_CONN, ECPG_SQLSTATE_CONNECTION_DOES_NOT_EXIST,
ecpg_gettext("NULL"));
va_end(args);
return false;
}
/* Make sure we do NOT honor the locale for numeric input */
/* since the database gives the standard decimal point */
/* (see comments in execute.c) */
@ -504,8 +514,6 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
setlocale(LC_NUMERIC, "C");
#endif
/* desperate try to guess something sensible */
stmt.connection = ecpg_get_connection(NULL);
ecpg_store_result(ECPGresult, index, &stmt, &data_var);
#ifdef HAVE_USELOCALE

@ -349,8 +349,12 @@ ecpg_deallocate_all_conn(int lineno, enum COMPAT_MODE c, struct connection *con)
bool
ECPGdeallocate_all(int lineno, int compat, const char *connection_name)
{
return ecpg_deallocate_all_conn(lineno, compat,
ecpg_get_connection(connection_name));
struct connection *con = ecpg_get_connection(connection_name);
if (!ecpg_init(con, connection_name, lineno))
return false;
return ecpg_deallocate_all_conn(lineno, compat, con);
}
char *
@ -363,13 +367,15 @@ ecpg_prepared(const char *name, struct connection *con)
}
/* return the prepared statement */
/* lineno is not used here, but kept in to not break API */
char *
ECPGprepared_statement(const char *connection_name, const char *name, int lineno)
{
(void) lineno; /* keep the compiler quiet */
struct connection *con = ecpg_get_connection(connection_name);
if (!ecpg_init(con, connection_name, lineno))
return NULL;
return ecpg_prepared(name, ecpg_get_connection(connection_name));
return ecpg_prepared(name, con);
}
/*
@ -466,10 +472,18 @@ ecpg_freeStmtCacheEntry(int lineno, int compat,
con = ecpg_get_connection(entry->connection);
/* free the 'prepared_statement' list entry */
this = ecpg_find_prepared_statement(entry->stmtID, con, &prev);
if (this && !deallocate_one(lineno, compat, con, prev, this))
return -1;
/*
* If the connection is gone, the prepared_statement list it owned is
* already unreachable, so just skip that cleanup. We must still clear
* the cache slot below so it can be reused.
*/
if (con)
{
/* free the 'prepared_statement' list entry */
this = ecpg_find_prepared_statement(entry->stmtID, con, &prev);
if (this && !deallocate_one(lineno, compat, con, prev, this))
return -1;
}
entry->stmtID[0] = '\0';

@ -8,3 +8,5 @@
/test4.c
/test5
/test5.c
/test6
/test6.c

@ -7,6 +7,7 @@ TESTS = test1 test1.c \
test2 test2.c \
test3 test3.c \
test4 test4.c \
test5 test5.c
test5 test5.c \
test6 test6.c
all: $(TESTS)

@ -0,0 +1,68 @@
/*
* This test verifies that ecpg functions properly handle NULL connections
* (i.e., when a connection name doesn't exist or has been disconnected).
* Before the fix, these operations would cause a segmentation fault.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
exec sql include ../regression;
int
main(void)
{
exec sql begin declare section;
int val1output = 2;
int val1 = 1;
char val2[6] = "data1";
char *stmt1 = "SELECT * from test1 where a = $1 and b = $2";
exec sql end declare section;
ECPGdebug(1, stderr);
/* Connect to the database */
exec sql connect to REGRESSDB1 as myconn;
/* Test 1: Try to get descriptor on a disconnected connection */
printf("Test 1: Try to get descriptor on a disconnected connection\n");
exec sql create table test1 (a int, b text);
exec sql insert into test1 (a,b) values (1, 'data1');
exec sql allocate descriptor indesc;
exec sql allocate descriptor outdesc;
exec sql prepare foo2 from :stmt1;
exec sql set descriptor indesc value 1 DATA = :val1;
exec sql set descriptor indesc value 2 DATA = :val2;
exec sql execute foo2 using sql descriptor indesc into sql descriptor outdesc;
exec sql rollback;
exec sql disconnect;
exec sql get descriptor outdesc value 1 :val1output = DATA;
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
/* Test 2: Try to deallocate all on a non-existent connection */
printf("Test 2: deallocate all with non-existent connection\n");
exec sql at nonexistent deallocate all;
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
/* Test 3: deallocate on disconnected connection */
printf("Test 3: deallocate all on disconnected connection\n");
exec sql deallocate all;
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
/* Test 4: Use prepared statement from non-existent connection */
printf("Test 4: Use prepared statement from non-existent connection\n");
exec sql at nonexistent prepare stmt1 FROM "SELECT 1";
exec sql at nonexistent declare cur1 cursor for stmt1;
exec sql at nonexistent open cur1;
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
printf("All tests completed !\n");
return 0;
}

@ -13,6 +13,7 @@ test: connect/test2
test: connect/test3
test: connect/test4
test: connect/test5
test: connect/test6
test: pgtypeslib/dt_test
test: pgtypeslib/dt_test2
test: pgtypeslib/num_test

@ -0,0 +1,146 @@
/* Processed by ecpg (regression mode) */
/* These include files are added by the preprocessor */
#include <ecpglib.h>
#include <ecpgerrno.h>
#include <sqlca.h>
/* End of automatic include section */
#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
#line 1 "test6.pgc"
/*
* This test verifies that ecpg functions properly handle NULL connections
* (i.e., when a connection name doesn't exist or has been disconnected).
* Before the fix, these operations would cause a segmentation fault.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#line 1 "regression.h"
#line 11 "test6.pgc"
int
main(void)
{
/* exec sql begin declare section */
#line 17 "test6.pgc"
int val1output = 2 ;
#line 18 "test6.pgc"
int val1 = 1 ;
#line 19 "test6.pgc"
char val2 [ 6 ] = "data1" ;
#line 20 "test6.pgc"
char * stmt1 = "SELECT * from test1 where a = $1 and b = $2" ;
/* exec sql end declare section */
#line 21 "test6.pgc"
ECPGdebug(1, stderr);
/* Connect to the database */
{ ECPGconnect(__LINE__, 0, "ecpg1_regression" , NULL, NULL , "myconn", 0); }
#line 26 "test6.pgc"
/* Test 1: Try to get descriptor on a disconnected connection */
printf("Test 1: Try to get descriptor on a disconnected connection\n");
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table test1 ( a int , b text )", ECPGt_EOIT, ECPGt_EORT);}
#line 30 "test6.pgc"
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into test1 ( a , b ) values ( 1 , 'data1' )", ECPGt_EOIT, ECPGt_EORT);}
#line 31 "test6.pgc"
ECPGallocate_desc(__LINE__, "indesc");
#line 33 "test6.pgc"
ECPGallocate_desc(__LINE__, "outdesc");
#line 34 "test6.pgc"
{ ECPGprepare(__LINE__, NULL, 0, "foo2", stmt1);}
#line 36 "test6.pgc"
{ ECPGset_desc(__LINE__, "indesc", 1,ECPGd_data,
ECPGt_int,&(val1),(long)1,(long)1,sizeof(int), ECPGd_EODT);
}
#line 38 "test6.pgc"
{ ECPGset_desc(__LINE__, "indesc", 2,ECPGd_data,
ECPGt_char,(val2),(long)6,(long)1,(6)*sizeof(char), ECPGd_EODT);
}
#line 39 "test6.pgc"
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_execute, "foo2",
ECPGt_descriptor, "indesc", 1L, 1L, 1L,
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT,
ECPGt_descriptor, "outdesc", 1L, 1L, 1L,
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);}
#line 41 "test6.pgc"
{ ECPGtrans(__LINE__, NULL, "rollback");}
#line 43 "test6.pgc"
{ ECPGdisconnect(__LINE__, "CURRENT");}
#line 44 "test6.pgc"
{ ECPGget_desc(__LINE__, "outdesc", 1,ECPGd_data,
ECPGt_int,&(val1output),(long)1,(long)1,sizeof(int), ECPGd_EODT);
}
#line 45 "test6.pgc"
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
/* Test 2: Try to deallocate all on a non-existent connection */
printf("Test 2: deallocate all with non-existent connection\n");
{ ECPGdeallocate_all(__LINE__, 0, "nonexistent");}
#line 50 "test6.pgc"
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
/* Test 3: deallocate on disconnected connection */
printf("Test 3: deallocate all on disconnected connection\n");
{ ECPGdeallocate_all(__LINE__, 0, NULL);}
#line 55 "test6.pgc"
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
/* Test 4: Use prepared statement from non-existent connection */
printf("Test 4: Use prepared statement from non-existent connection\n");
{ ECPGprepare(__LINE__, "nonexistent", 0, "stmt1", "SELECT 1");}
#line 60 "test6.pgc"
/* declare cur1 cursor for $1 */
#line 61 "test6.pgc"
{ ECPGdo(__LINE__, 0, 1, "nonexistent", 0, ECPGst_normal, "declare cur1 cursor for $1",
ECPGt_char_variable,(ECPGprepared_statement("nonexistent", "stmt1", __LINE__)),(long)1,(long)1,(1)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);}
#line 62 "test6.pgc"
printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode);
printf("All tests completed !\n");
return 0;
}

@ -0,0 +1,50 @@
[NO_PID]: ECPGdebug: set to 1
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ECPGconnect: opening database ecpg1_regression on <DEFAULT> port <DEFAULT>
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_execute on line 30: query: create table test1 ( a int , b text ); with 0 parameter(s) on connection myconn
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_execute on line 30: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_process_output on line 30: OK: CREATE TABLE
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_execute on line 31: query: insert into test1 ( a , b ) values ( 1 , 'data1' ); with 0 parameter(s) on connection myconn
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_execute on line 31: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_process_output on line 31: OK: INSERT 0 1
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: prepare_common on line 36: name foo2; query: "SELECT * from test1 where a = $1 and b = $2"
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_execute on line 41: query: SELECT * from test1 where a = $1 and b = $2; with 2 parameter(s) on connection myconn
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_execute on line 41: using PQexecPrepared for "SELECT * from test1 where a = $1 and b = $2"
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_free_params on line 41: parameter 1 = 1
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_free_params on line 41: parameter 2 = data1
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_process_output on line 41: correctly got 1 tuples with 2 fields
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_process_output on line 41: putting result (1 tuples) into descriptor outdesc
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ECPGtrans on line 43: action "rollback"; connection "myconn"
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: deallocate_one on line 0: name foo2
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_finish: connection myconn closed
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ECPGget_desc: reading items for tuple 1
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: raising sqlcode -220 on line 45: connection "NULL" does not exist on line 45
[NO_PID]: sqlca: code: -220, state: 08003
[NO_PID]: raising sqlcode -220 on line 50: connection "nonexistent" does not exist on line 50
[NO_PID]: sqlca: code: -220, state: 08003
[NO_PID]: raising sqlcode -220 on line 55: connection "NULL" does not exist on line 55
[NO_PID]: sqlca: code: -220, state: 08003
[NO_PID]: raising sqlcode -220 on line 60: connection "nonexistent" does not exist on line 60
[NO_PID]: sqlca: code: -220, state: 08003
[NO_PID]: raising sqlcode -220 on line 63: connection "nonexistent" does not exist on line 63
[NO_PID]: sqlca: code: -220, state: 08003
[NO_PID]: raising sqlcode -220 on line 62: connection "nonexistent" does not exist on line 62
[NO_PID]: sqlca: code: -220, state: 08003

@ -0,0 +1,9 @@
Test 1: Try to get descriptor on a disconnected connection
sqlca.sqlcode = -220
Test 2: deallocate all with non-existent connection
sqlca.sqlcode = -220
Test 3: deallocate all on disconnected connection
sqlca.sqlcode = -220
Test 4: Use prepared statement from non-existent connection
sqlca.sqlcode = -220
All tests completed !
Loading…
Cancel
Save