]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add missing connection validation in ECPG
authorAndrew Dunstan <andrew@dunslane.net>
Fri, 1 May 2026 19:12:28 +0000 (15:12 -0400)
committerAndrew Dunstan <andrew@dunslane.net>
Fri, 1 May 2026 19:12:28 +0000 (15:12 -0400)
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

src/interfaces/ecpg/ecpglib/descriptor.c
src/interfaces/ecpg/ecpglib/prepare.c
src/interfaces/ecpg/test/connect/.gitignore
src/interfaces/ecpg/test/connect/Makefile
src/interfaces/ecpg/test/connect/meson.build
src/interfaces/ecpg/test/connect/test6.pgc [new file with mode: 0644]
src/interfaces/ecpg/test/ecpg_schedule
src/interfaces/ecpg/test/expected/connect-test6.c [new file with mode: 0644]
src/interfaces/ecpg/test/expected/connect-test6.stderr [new file with mode: 0644]
src/interfaces/ecpg/test/expected/connect-test6.stdout [new file with mode: 0644]

index 39cd5130ec97ab08bb7036cd1fd66a4084ca8cdc..1ad5f2d88cc316332bbb3887f2c49c6bf7f6fe6f 100644 (file)
@@ -476,6 +476,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) */
@@ -505,8 +515,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
index 5c7c5397535d8a3e76e54b51d0551ba5811b37cb..3874a64ae8e8845777cfcdf48af0960b04c4010a 100644 (file)
@@ -381,8 +381,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 *
@@ -395,13 +399,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);
 }
 
 /*
@@ -498,10 +504,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';
 
index e0639f3c8d189eb925f8576b828b7e1f589dbbe0..02236847444620388a6642f3e607a9bc78f9ae42 100644 (file)
@@ -8,3 +8,5 @@
 /test4.c
 /test5
 /test5.c
+/test6
+/test6.c
index 2602d5d286f89a36ff6cf81f621ca8bba1d5fb9d..17fa2667bf73460eb790132efc3638b317b004a1 100644 (file)
@@ -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)
index 591e04bc422ae41aef2a088de306e2b0446c9919..1cc0a928309af1f6bbbeddded7f240db87bf36b6 100644 (file)
@@ -6,6 +6,7 @@ pgc_files = [
   'test3',
   'test4',
   'test5',
+  'test6',
 ]
 
 foreach pgc_file : pgc_files
diff --git a/src/interfaces/ecpg/test/connect/test6.pgc b/src/interfaces/ecpg/test/connect/test6.pgc
new file mode 100644 (file)
index 0000000..d2c10df
--- /dev/null
@@ -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;
+}
index b75e16fde1e72ac1dac57386e04f160bff3767ef..d1f5d9452b76ef660b03e696536b3dff4dfbb2bb 100644 (file)
@@ -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
diff --git a/src/interfaces/ecpg/test/expected/connect-test6.c b/src/interfaces/ecpg/test/expected/connect-test6.c
new file mode 100644 (file)
index 0000000..eed3c46
--- /dev/null
@@ -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;
+}
diff --git a/src/interfaces/ecpg/test/expected/connect-test6.stderr b/src/interfaces/ecpg/test/expected/connect-test6.stderr
new file mode 100644 (file)
index 0000000..8784d5a
--- /dev/null
@@ -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
diff --git a/src/interfaces/ecpg/test/expected/connect-test6.stdout b/src/interfaces/ecpg/test/expected/connect-test6.stdout
new file mode 100644 (file)
index 0000000..bf9a2e9
--- /dev/null
@@ -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 !