Thread: BUG #5697: Infinite loop inside PQexecStart function

BUG #5697: Infinite loop inside PQexecStart function

From
"Boris"
Date:
The following bug has been logged online:

Bug reference:      5697
Logged by:          Boris
Email address:      admin@nyc.yamaha.com
PostgreSQL version: 8.3.5
Operating system:   Linux RH ES5
Description:        Infinite loop inside PQexecStart function
Details:

The infinite loop in this case occurs inside the PQexecStart() function in
pgsql driver. The following insert corresponds to the actual infinite loop.
The are several conditions that are checked "(result = PQgetResult(conn)) !=
NULL" and "result->resultStatus == PGRES_COPY_IN || result->resultStatus ==
PGRES_COPY_OUT || conn->status == CONNECTION_BAD" as an exit point.
---------------------------------------------------------
ASM code
0x0042bca5 in PQexecStart () from /usr/local/pgsql/lib/libpq.so.5
 17 0x0042bca5 <PQexecStart+37>: cmp $0x3,%esi
 18 0x0042bca8 <PQexecStart+40>: je 0x42bd00 <PQexecStart+128>
 19 0x0042bcaa <PQexecStart+42>: cmpl $0x1,0x44(%edi)
 20 0x0042bcae <PQexecStart+46>: je 0x42bcf0 <PQexecStart+112>
 21 0x0042bcb0 <PQexecStart+48>: mov %edi,(%esp)
 22 0x0042bcb3 <PQexecStart+51>: call 0x424fa0 <PQgetResult@plt>
 23 0x0042bcb8 <PQexecStart+56>: test %eax,%eax
 24 0x0042bcba <PQexecStart+58>: je 0x42bd13 <PQexecStart+147>
 25 0x0042bcbc <PQexecStart+60>: mov 0x1c(%eax),%esi
 26 0x0042bcbf <PQexecStart+63>: mov %eax,(%esp)
 27 0x0042bcc2 <PQexecStart+66>: call 0x4255d0 <PQclear@plt>
 28 0x0042bcc7 <PQexecStart+71>: cmp $0x4,%esi
 29 0x0042bcca <PQexecStart+74>: jne 0x42bca5 <PQexecStart+37>

---------------------------------------------------------
The C-code corresponding to this part (short version):
while ((result = PQgetResult(conn)) != NULL){
    ExecStatusType resultStatus = result->resultStatus;
    PQclear(result); /* only need its status */
    /* check for loss of connection, too */
    if (result->resultStatus == PGRES_COPY_IN ||
                         result->resultStatus == PGRES_COPY_OUT ||
                         conn->status == CONNECTION_BAD)
                         break;
    }
    return true;
---------------------------------------------------------
These are the values mapped to the corresponding constants:
PGRES_EMPTY_QUERY = 0
PGRES_COMMAND_OK = 1
PGRES_TUPLES_OK = 2
PGRES_COPY_OUT = 3
PGRES_COPY_IN = 4
PGRES_BAD_RESPONSE = 5
PGRES_NONFATAL_ERROR = 6
PGRES_FATAL_ERROR = 7

Condition exit point is evaluated against 3 constants PGRES_COPY_IN,
PGRES_COPY_OUT, CONNECTION_BAD. Since the connection to the database is in a
"GOOD" state the only constants that are evaluated are PGRES_COPY_IN and
PGRES_COPY_OUT, but according to the debugger trace the value those are
compared against is 7, e.g. PGRES_FATAL_ERROR which has no condition and
thus the process runs forever. Please see the following insert with detailed
output of the registers.
---------------------------------------------------------
0x0042bcc7 in PQexecStart () from /usr/local/pgsql/lib/libpq.so.5
1: x/i $pc 0x42bcc7 <PQexecStart+71>:    cmp $0x4,%esi
(gdb) i r
eax 0x99    153
ecx 0x1    1
edx 0x98    152
ebx 0x43a330    4432688
esp 0xbfe506d0    0xbfe506d0
ebp 0xbfe506e8    0xbfe506e8
esi 0x7    7
edi 0x98c5bb4    160193460
eip 0x42bcc7    0x42bcc7 <PQexecStart+71>
eflags 0x286    [ PF SF IF ]
cs 0x73    115
ss 0x7b    123
ds 0x7b    123
es 0x7b    123
fs 0x0    0
gs 0x33    51

Re: BUG #5697: Infinite loop inside PQexecStart function

From
Tom Lane
Date:
"Boris" <admin@nyc.yamaha.com> writes:
> while ((result = PQgetResult(conn)) != NULL){
>     ExecStatusType resultStatus = result->resultStatus;
>     PQclear(result); /* only need its status */
>     /* check for loss of connection, too */
>     if (result->resultStatus == PGRES_COPY_IN ||
>                          result->resultStatus == PGRES_COPY_OUT ||
>                          conn->status == CONNECTION_BAD)
>                          break;
>     }

This code is broken: once you've done PQclear() it's unsafe to access
the PGresult.  I think you meant just "resultStatus" not
"result->resultStatus" in the if().

            regards, tom lane

Re: BUG #5697: Infinite loop inside PQexecStart function

From
Tom Lane
Date:
Boris Bondarenko <bbondarenko@nyc.yamaha.com> writes:
> That was a short form i used to point out the issue, the actual code
> from  src/interfaces/libpq/fe-exec.c

Oh.  Well, you didn't actually explain under what conditions you think
that fails.  Since that code hasn't changed in quite a few releases,
and is extremely heavily exercised every day, the burden is on you
to demonstrate that there's a problem.

            regards, tom lane

Re: BUG #5697: Infinite loop inside PQexecStart function

From
Boris Bondarenko
Date:
That was a short form i used to point out the issue, the actual code
from  src/interfaces/libpq/fe-exec.c

    1368 static bool
    1369 PQexecStart(PGconn *conn)
    1370 {
    1371         PGresult   *result;
    1372
    1373         if (!conn)
    1374                 return false;
    1375
    1376         /*
    1377          * Silently discard any prior query result that
application didn't eat.
    1378          * This is probably poor design, but it's here for
backward compatibility.
    1379          */
    1380         while ((result = PQgetResult(conn)) != NULL)
    1381         {
    1382                 ExecStatusType resultStatus = result->resultStatus;
    1383
    1384                 PQclear(result);                /* only need
its status */
    1385                 if (resultStatus == PGRES_COPY_IN)
    1386                 {
    1387                         if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
    1388                         {
    1389                                 /* In protocol 3, we can get
out of a COPY IN state */
    1390                                 if (PQputCopyEnd(conn,
    1391
libpq_gettext("COPY terminated by new PQexec")) < 0)
    1392                                         return false;
    1393                                 /* keep waiting to swallow the
copy's failure message */
    1394                         }
    1395                         else
    1396                         {
    1397                                 /* In older protocols we have
to punt */
    1398
printfPQExpBuffer(&conn->errorMessage,
    1399                                   libpq_gettext("COPY IN state
must be terminated first\n"));
    1400                                 return false;
    1401                         }
    1402                 }
    1403                 else if (resultStatus == PGRES_COPY_OUT)
    1404                 {
    1405                         if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
    1406                         {
    1407                                 /*
    1408                                  * In protocol 3, we can get
out of a COPY OUT state: we just
    1409                                  * switch back to BUSY and
allow the remaining COPY data to be
    1410                                  * dropped on the floor.
    1411                                  */
    1412                                 conn->asyncStatus = PGASYNC_BUSY;
    1413                                 /* keep waiting to swallow the
copy's completion message */
    1414                         }
    1415                         else
    1416                         {
    1417                                 /* In older protocols we have
to punt */
    1418
printfPQExpBuffer(&conn->errorMessage,
    1419                                  libpq_gettext("COPY OUT state
must be terminated first\n"));
    1420                                 return false;
    1421                         }
    1422                 }
    1423                 /* check for loss of connection, too */
    1424                 if (conn->status == CONNECTION_BAD)
    1425                         return false;
    1426         }
    1427
    1428         /* OK to send a command */
    1429         return true;
    1430 }


Sorry for the confusion from the shortened form.


Tom Lane wrote:
> "Boris" <admin@nyc.yamaha.com> writes:
>> while ((result = PQgetResult(conn)) != NULL){
>>     ExecStatusType resultStatus = result->resultStatus;
>>     PQclear(result); /* only need its status */
>>     /* check for loss of connection, too */
>>     if (result->resultStatus == PGRES_COPY_IN ||
>>                          result->resultStatus == PGRES_COPY_OUT ||
>>                          conn->status == CONNECTION_BAD)
>>                          break;
>>     }
>
> This code is broken: once you've done PQclear() it's unsafe to access
> the PGresult.  I think you meant just "resultStatus" not
> "result->resultStatus" in the if().
>
>             regards, tom lane
>

--
Boris Bondarenko -:- bbondarenko@nyc.yamaha.com
    Yamaha Music Interactive Inc.

YMIA