From b1ea82fee14c457a76ac65efca58155240dab950 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 14 Dec 2005 17:07:00 +0000 Subject: [PATCH] Defend against crash while processing Describe Statement or Describe Portal 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 | 26 ++++++++++++++++- src/backend/tcop/postgres.c | 53 ++++++++++++++++++++++++++++++++-- src/include/commands/prepare.h | 3 +- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index babae5e1483..b301a75b276 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -10,7 +10,7 @@ * Copyright (c) 2002-2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.23.4.2 2004/12/13 00:17:52 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.23.4.3 2005/12/14 17:06:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -442,6 +442,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; +} + /* * Implements the 'DEALLOCATE' utility statement: deletes the * specified plan from storage. diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0526d3c8331..6f424f0976b 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.375.2.4 2005/11/10 00:31:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.375.2.5 2005/12/14 17:07:00 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -1644,6 +1644,15 @@ exec_describe_statement_message(const char *stmt_name) List *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); @@ -1657,6 +1666,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 != Remote) return; /* can't actually do anything... */ @@ -1703,12 +1728,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 != Remote) return; /* can't actually do anything... */ @@ -2715,7 +2764,7 @@ PostgresMain(int argc, char *argv[], const char *username) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.375.2.4 $ $Date: 2005/11/10 00:31:59 $\n"); + puts("$Revision: 1.375.2.5 $ $Date: 2005/12/14 17:07:00 $\n"); } /* diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index fdbea2c6083..5c1fcfaa02e 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -6,7 +6,7 @@ * * Copyright (c) 2002-2003, PostgreSQL Global Development Group * - * $Id: prepare.h,v 1.8 2003/08/08 21:42:40 momjian Exp $ + * $Id: prepare.h,v 1.8.4.1 2005/12/14 17:07:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -58,5 +58,6 @@ 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); #endif /* PREPARE_H */