Defend against crash while processing Describe Statement or Describe Portal
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Dec 2005 17:06:37 +0000 (17:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Dec 2005 17:06:37 +0000 (17:06 +0000)
messages, when client attempts to execute these outside a transaction (start
one) or in a failed transaction (reject message, except for COMMIT/ROLLBACK
statements which we can handle).  Per report from Francisco Figueiredo Jr.

src/backend/commands/prepare.c
src/backend/tcop/postgres.c
src/include/commands/prepare.h

index e284feb67c9a6626cf9c8871a6ec82e6a213da92..2ece899a94c9d82fd856321b2856a315d21c8352 100644 (file)
@@ -445,6 +445,30 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt)
        return NULL;
 }
 
+/*
+ * Given a prepared statement, determine whether it will return tuples.
+ *
+ * Note: this is used rather than just testing the result of
+ * FetchPreparedStatementResultDesc() because that routine can fail if
+ * invoked in an aborted transaction.  This one is safe to use in any
+ * context.  Be sure to keep the two routines in sync!
+ */
+bool
+PreparedStatementReturnsTuples(PreparedStatement *stmt)
+{
+       switch (ChoosePortalStrategy(stmt->query_list))
+       {
+               case PORTAL_ONE_SELECT:
+               case PORTAL_UTIL_SELECT:
+                       return true;
+
+               case PORTAL_MULTI_QUERY:
+                       /* will not return tuples */
+                       break;
+       }
+       return false;
+}
+
 /*
  * Given a prepared statement that returns tuples, extract the query
  * targetlist. Returns NIL if the statement doesn't have a determinable
index f46bfdf4e176bb592d47e319f8cef83ab5ec77d8..33c5b1d81ca74164dbee016143c49695c88aa70e 100644 (file)
@@ -1849,6 +1849,15 @@ exec_describe_statement_message(const char *stmt_name)
        ListCell   *l;
        StringInfoData buf;
 
+       /*
+        * Start up a transaction command. (Note that this will normally change
+        * current memory context.) Nothing happens if we are already in one.
+        */
+       start_xact_command();
+
+       /* Switch back to message context */
+       MemoryContextSwitchTo(MessageContext);
+
        /* Find prepared statement */
        if (stmt_name[0] != '\0')
                pstmt = FetchPreparedStatement(stmt_name, true);
@@ -1862,6 +1871,22 @@ exec_describe_statement_message(const char *stmt_name)
                                         errmsg("unnamed prepared statement does not exist")));
        }
 
+       /*
+        * If we are in aborted transaction state, we can't safely create a result
+        * tupledesc, because that needs catalog accesses.  Hence, refuse to
+        * Describe statements that return data.  (We shouldn't just refuse all
+        * Describes, since that might break the ability of some clients to issue
+        * COMMIT or ROLLBACK commands, if they use code that blindly Describes
+        * whatever it does.)  We can Describe parameters without doing anything
+        * dangerous, so we don't restrict that.
+        */
+       if (IsAbortedTransactionBlockState() &&
+               PreparedStatementReturnsTuples(pstmt))
+               ereport(ERROR,
+                               (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
+                                errmsg("current transaction is aborted, "
+                                               "commands ignored until end of transaction block")));
+
        if (whereToSendOutput != DestRemote)
                return;                                 /* can't actually do anything... */
 
@@ -1902,12 +1927,36 @@ exec_describe_portal_message(const char *portal_name)
 {
        Portal          portal;
 
+       /*
+        * Start up a transaction command. (Note that this will normally change
+        * current memory context.) Nothing happens if we are already in one.
+        */
+       start_xact_command();
+
+       /* Switch back to message context */
+       MemoryContextSwitchTo(MessageContext);
+
        portal = GetPortalByName(portal_name);
        if (!PortalIsValid(portal))
                ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_CURSOR),
                                 errmsg("portal \"%s\" does not exist", portal_name)));
 
+       /*
+        * If we are in aborted transaction state, we can't run
+        * SendRowDescriptionMessage(), because that needs catalog accesses.
+        * Hence, refuse to Describe portals that return data.  (We shouldn't just
+        * refuse all Describes, since that might break the ability of some
+        * clients to issue COMMIT or ROLLBACK commands, if they use code that
+        * blindly Describes whatever it does.)
+        */
+       if (IsAbortedTransactionBlockState() &&
+               portal->tupDesc)
+               ereport(ERROR,
+                               (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
+                                errmsg("current transaction is aborted, "
+                                               "commands ignored until end of transaction block")));
+
        if (whereToSendOutput != DestRemote)
                return;                                 /* can't actually do anything... */
 
index 2bac73b49bd0af764528ba97443fa8e18b79c717..3890e1882c8be07fbcf5069a17d5f37197678b0d 100644 (file)
@@ -59,6 +59,7 @@ extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
 extern void DropPreparedStatement(const char *stmt_name, bool showError);
 extern List *FetchPreparedStatementParams(const char *stmt_name);
 extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
+extern bool PreparedStatementReturnsTuples(PreparedStatement *stmt);
 extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
 
 #endif   /* PREPARE_H */