Skip to content

Commit ec91ee8

Browse files
committed
Fix access-to-already-freed-memory issue in plpython's error handling.
PLy_elog() could attempt to access strings that Python had already freed, because the strings that PLy_get_spi_error_data() returns are simply pointers into storage associated with the error "val" PyObject. That's fine at the instant PLy_get_spi_error_data() returns them, but just after that PLy_traceback() intentionally releases the only refcount on that object, allowing it to be freed --- so that the strings we pass to ereport() are dangling pointers. In principle this could result in garbage output or a coredump. In practice, I think the risk is pretty low, because there are no Python operations between where we decrement that refcount and where we use the strings (and copy them into PG storage), and thus no reason for Python to recycle the storage. Still, it's clearly hazardous, and it leads to Valgrind complaints when running under a Valgrind that hasn't been lobotomized to ignore Python memory allocations. The code was a mess anyway: we fetched the error data out of Python (clearing Python's error indicator) with PyErr_Fetch, examined it, pushed it back into Python with PyErr_Restore (re-setting the error indicator), then immediately pulled it back out with another PyErr_Fetch. Just to confuse matters even more, there were some gratuitous-and-yet-hazardous PyErr_Clear calls in the "examine" step, and we didn't get around to doing PyErr_NormalizeException until after the second PyErr_Fetch, making it even less clear which object was being manipulated where and whether we still had a refcount on it. (If PyErr_NormalizeException did substitute a different "val" object, it's possible that the problem could manifest for real, because then we'd be doing assorted Python stuff with no refcount on the object we have string pointers into.) So, rearrange all that into some semblance of sanity, and don't decrement the refcount on the Python error objects until the end of PLy_elog(). In HEAD, I failed to resist the temptation to reformat some messy bits from 5c3c3cd along the way. Back-patch as far as 9.2, because the code is substantially the same that far back. I believe that 9.1 has the bug as well; but the code around it is rather different and I don't want to take a chance on breaking something for what seems a low-probability problem.
1 parent bf73016 commit ec91ee8

File tree

1 file changed

+21
-21
lines changed

1 file changed

+21
-21
lines changed

src/pl/plpython/plpy_elog.c

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ PyObject *PLy_exc_fatal = NULL;
2121
PyObject *PLy_exc_spi_error = NULL;
2222

2323

24-
static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
24+
static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
25+
char **xmsg, char **tbmsg, int *tb_depth);
2526
static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
2627
char **hint, char **query, int *position);
2728
static char *get_source_line(const char *src, int lineno);
@@ -53,16 +54,20 @@ PLy_elog(int elevel, const char *fmt,...)
5354
int position = 0;
5455

5556
PyErr_Fetch(&exc, &val, &tb);
57+
5658
if (exc != NULL)
5759
{
60+
PyErr_NormalizeException(&exc, &val, &tb);
61+
5862
if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
5963
PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
6064
else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
6165
elevel = FATAL;
6266
}
63-
PyErr_Restore(exc, val, tb);
6467

65-
PLy_traceback(&xmsg, &tbmsg, &tb_depth);
68+
/* this releases our refcount on tb! */
69+
PLy_traceback(exc, val, tb,
70+
&xmsg, &tbmsg, &tb_depth);
6671

6772
if (fmt)
6873
{
@@ -113,6 +118,9 @@ PLy_elog(int elevel, const char *fmt,...)
113118
pfree(xmsg);
114119
if (tbmsg)
115120
pfree(tbmsg);
121+
Py_XDECREF(exc);
122+
Py_XDECREF(val);
123+
116124
PG_RE_THROW();
117125
}
118126
PG_END_TRY();
@@ -123,21 +131,24 @@ PLy_elog(int elevel, const char *fmt,...)
123131
pfree(xmsg);
124132
if (tbmsg)
125133
pfree(tbmsg);
134+
Py_XDECREF(exc);
135+
Py_XDECREF(val);
126136
}
127137

128138
/*
129-
* Extract a Python traceback from the current exception.
139+
* Extract a Python traceback from the given exception data.
130140
*
131141
* The exception error message is returned in xmsg, the traceback in
132142
* tbmsg (both as palloc'd strings) and the traceback depth in
133143
* tb_depth.
144+
*
145+
* We release refcounts on all the Python objects in the traceback stack,
146+
* but not on e or v.
134147
*/
135148
static void
136-
PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
149+
PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
150+
char **xmsg, char **tbmsg, int *tb_depth)
137151
{
138-
PyObject *e,
139-
*v,
140-
*tb;
141152
PyObject *e_type_o;
142153
PyObject *e_module_o;
143154
char *e_type_s = NULL;
@@ -148,12 +159,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
148159
StringInfoData tbstr;
149160

150161
/*
151-
* get the current exception
152-
*/
153-
PyErr_Fetch(&e, &v, &tb);
154-
155-
/*
156-
* oops, no exception, return
162+
* if no exception, return nulls
157163
*/
158164
if (e == NULL)
159165
{
@@ -164,8 +170,6 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
164170
return;
165171
}
166172

167-
PyErr_NormalizeException(&e, &v, &tb);
168-
169173
/*
170174
* Format the exception and its value and put it in xmsg.
171175
*/
@@ -332,8 +336,6 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
332336
Py_XDECREF(e_type_o);
333337
Py_XDECREF(e_module_o);
334338
Py_XDECREF(vob);
335-
Py_XDECREF(v);
336-
Py_DECREF(e);
337339
}
338340

339341
/*
@@ -367,7 +369,7 @@ PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
367369
static void
368370
PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position)
369371
{
370-
PyObject *spidata = NULL;
372+
PyObject *spidata;
371373

372374
spidata = PyObject_GetAttrString(exc, "spidata");
373375

@@ -384,8 +386,6 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
384386
PLy_get_spi_sqlerrcode(exc, sqlerrcode);
385387
}
386388

387-
PyErr_Clear();
388-
/* no elog here, we simply won't report the errhint, errposition etc */
389389
Py_XDECREF(spidata);
390390
}
391391

0 commit comments

Comments
 (0)