Skip to content

Commit ca204ea

Browse files
author
James William Pye
committed
Add some overflow checks for cat_messages.
Some additional work is still necessary in this area, but this should cover the concerned corners. More tests would be nice. Some attention should be paid to Py_ssize_t size for size_t size, and often the casts to uint32_t may not be appropriate. However, these would be substantially strange circumstances considering that such a thing would mean that a gigabyte sized bytes() object was passed in. Fixes #26
1 parent 1d2bb7d commit ca204ea

2 files changed

Lines changed: 65 additions & 29 deletions

File tree

postgresql/port/optimized/element3.c

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,40 @@ pack_tuple_data(PyObject *self, PyObject *tup)
241241
return(_pack_tuple_data(tup));
242242
}
243243

244+
/*
245+
* Check for overflow before incrementing the buffer size for cat_messages.
246+
*/
247+
#define INCSIZET(XVAR, AMT) do { \
248+
size_t _amt_ = AMT; \
249+
size_t _newsize_ = XVAR + _amt_; \
250+
if (_newsize_ >= XVAR) XVAR = _newsize_; else { \
251+
PyErr_Format(PyExc_OverflowError, \
252+
"buffer size overflowed, was %zd bytes, but could not add %d more", XVAR, _amt_); \
253+
goto fail; } \
254+
} while(0)
255+
256+
#define INCMSGSIZE(XVAR, AMT) do { \
257+
uint32_t _amt_ = AMT; \
258+
uint32_t _newsize_ = XVAR + _amt_; \
259+
if (_newsize_ >= XVAR) XVAR = _newsize_; else { \
260+
PyErr_Format(PyExc_OverflowError, \
261+
"message size too large, was %u bytes, but could not add %u more", XVAR, _amt_); \
262+
goto fail; } \
263+
} while(0)
264+
265+
/*
266+
* cat_messages - cat the serialized form of the messages in the given list
267+
*
268+
* This offers a fast way to construct the final bytes() object to be sent to
269+
* the wire. It avoids re-creating bytes() objects by calculating the serialized
270+
* size of contiguous, homogenous messages, allocating or extending the buffer
271+
* to accommodate for the needed size, and finally, copying the data into the
272+
* newly available space.
273+
*/
244274
static PyObject *
245275
cat_messages(PyObject *self, PyObject *messages_in)
246276
{
247-
const static uint32_t null_attribute = 0xFFFFFFFFL;
277+
const static char null_attribute[4] = {0xff,0xff,0xff,0xff};
248278
PyObject *msgs = NULL;
249279
Py_ssize_t nmsgs = 0;
250280
Py_ssize_t cmsg = 0;
@@ -254,15 +284,16 @@ cat_messages(PyObject *self, PyObject *messages_in)
254284
*/
255285
char *buf = NULL;
256286
char *nbuf = NULL;
257-
Py_ssize_t bufsize = 0;
258-
Py_ssize_t bufpos = 0;
287+
size_t bufsize = 0;
288+
size_t bufpos = 0;
259289

260290
/*
261291
* Get a List object for faster rescanning when dealing with copy data.
262292
*/
263293
msgs = PyObject_CallFunctionObjArgs((PyObject *) &PyList_Type, messages_in, NULL);
264294
if (msgs == NULL)
265295
return(NULL);
296+
266297
nmsgs = PyList_GET_SIZE(msgs);
267298

268299
while (cmsg < nmsgs)
@@ -276,13 +307,13 @@ cat_messages(PyObject *self, PyObject *messages_in)
276307
if (PyBytes_CheckExact(ob))
277308
{
278309
Py_ssize_t eofc = cmsg;
279-
Py_ssize_t xsize = 0;
310+
size_t xsize = 0;
280311
/* find the last of the copy data (eofc) */
281312
do
282313
{
283314
++eofc;
284315
/* increase in size to allocate for the adjacent copy messages */
285-
xsize += PyBytes_GET_SIZE(ob);
316+
INCSIZET(xsize, PyBytes_GET_SIZE(ob));
286317
if (eofc >= nmsgs)
287318
break; /* end of messages in the list? */
288319

@@ -296,8 +327,8 @@ cat_messages(PyObject *self, PyObject *messages_in)
296327
*/
297328

298329
/* realloc the buf for the new copy data */
299-
xsize = xsize + (5 * (eofc - cmsg));
300-
bufsize = bufsize + xsize;
330+
INCSIZET(xsize, (5 * (eofc - cmsg)));
331+
INCSIZET(bufsize, xsize);
301332
nbuf = realloc(buf, bufsize);
302333
if (nbuf == NULL)
303334
{
@@ -320,17 +351,17 @@ cat_messages(PyObject *self, PyObject *messages_in)
320351
*/
321352
while (cmsg < eofc)
322353
{
323-
uint32_t msg_length;
354+
uint32_t msg_length = 0;
324355
char *localbuf = buf + bufpos + 1;
325356
buf[bufpos] = 'd'; /* COPY data message type */
326357

327358
ob = PyList_GET_ITEM(msgs, cmsg);
328-
msg_length = PyBytes_GET_SIZE(ob) + 4;
359+
INCMSGSIZE(msg_length, (uint32_t) PyBytes_GET_SIZE(ob) + 4);
329360

330-
bufpos = bufpos + 1 + msg_length;
361+
INCSIZET(bufpos, 1 + msg_length);
331362
msg_length = local_ntohl(msg_length);
332-
memcpy(localbuf, &msg_length, 4);
333-
memcpy(localbuf + 4, PyBytes_AS_STRING(ob), PyBytes_GET_SIZE(ob));
363+
Py_MEMCPY(localbuf, &msg_length, 4);
364+
Py_MEMCPY(localbuf + 4, PyBytes_AS_STRING(ob), PyBytes_GET_SIZE(ob));
334365
++cmsg;
335366
}
336367
}
@@ -340,7 +371,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
340371
* Handle 'D' tuple data from a raw Python tuple.
341372
*/
342373
Py_ssize_t eofc = cmsg;
343-
Py_ssize_t xsize = 0;
374+
size_t xsize = 0;
344375

345376
/* find the last of the tuple data (eofc) */
346377
do
@@ -359,17 +390,17 @@ cat_messages(PyObject *self, PyObject *messages_in)
359390
* The items take *at least* 4 bytes each.
360391
* (The attribute count is considered later)
361392
*/
362-
xsize = xsize + (nitems * 4);
393+
INCSIZET(xsize, (nitems * 4));
363394

364395
for (current_item = 0; current_item < nitems; ++current_item)
365396
{
366397
PyObject *att = PyTuple_GET_ITEM(ob, current_item);
367398

368399
/*
369-
* Attributes are expected to be bytes() or None.
400+
* Attributes *must* be bytes() or None.
370401
*/
371402
if (PyBytes_CheckExact(att))
372-
xsize = xsize + PyBytes_GET_SIZE(att);
403+
INCSIZET(xsize, PyBytes_GET_SIZE(att));
373404
else if (att != Py_None)
374405
{
375406
PyErr_Format(PyExc_TypeError,
@@ -378,7 +409,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
378409
goto fail;
379410
}
380411
/*
381-
* else it's Py_None and the size has been specified.
412+
* else it's Py_None and the size will be included later.
382413
*/
383414
}
384415

@@ -403,15 +434,15 @@ cat_messages(PyObject *self, PyObject *messages_in)
403434
* 4 for the message size
404435
* 2 for the attribute count
405436
*/
406-
xsize = xsize + (7 * (eofc - cmsg));
407-
bufsize = bufsize + xsize;
437+
INCSIZET(xsize, (7 * (eofc - cmsg)));
438+
INCSIZET(bufsize, xsize);
408439
nbuf = realloc(buf, bufsize);
409440
if (nbuf == NULL)
410441
{
411442
PyErr_Format(
412443
PyExc_MemoryError,
413-
"failed to allocate %lu bytes of memory for out-going messages",
414-
(unsigned long) bufsize
444+
"failed to allocate %zd bytes of memory for out-going messages",
445+
bufsize
415446
);
416447
goto fail;
417448
}
@@ -433,7 +464,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
433464
Py_ssize_t current_item, nitems;
434465
uint32_t msg_length, out_msg_len;
435466
uint16_t natts;
436-
char *localbuf = buf + bufpos + 5; /* skipping the header for now */
467+
char *localbuf = (buf + bufpos) + 5; /* skipping the header for now */
437468
buf[bufpos] = 'D'; /* Tuple data message type */
438469

439470
ob = PyList_GET_ITEM(msgs, cmsg);
@@ -474,7 +505,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
474505
Py_MEMCPY(localbuf, PyBytes_AS_STRING(att), attsize);
475506
localbuf = localbuf + attsize;
476507

477-
msg_length = msg_length + attsize;
508+
INCSIZET(msg_length, attsize);
478509
}
479510
}
480511

@@ -488,7 +519,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
488519
* Filled in the data while summing the message size, so
489520
* adjust the buffer position for the next message.
490521
*/
491-
bufpos = bufpos + 1 + msg_length;
522+
INCSIZET(bufpos, 1 + msg_length);
492523
++cmsg;
493524
}
494525
}
@@ -542,7 +573,8 @@ cat_messages(PyObject *self, PyObject *messages_in)
542573
msg_type_size = PyBytes_GET_SIZE(msg_type);
543574

544575
/* realloc the buf for the new copy data */
545-
bufsize = bufsize + 4 + msg_type_size + PyBytes_GET_SIZE(serialized);
576+
INCSIZET(bufsize, 4 + msg_type_size);
577+
INCSIZET(bufsize, PyBytes_GET_SIZE(serialized));
546578
nbuf = realloc(buf, bufsize);
547579
if (nbuf == NULL)
548580
{
@@ -565,10 +597,11 @@ cat_messages(PyObject *self, PyObject *messages_in)
565597
* All necessary information acquired, so fill in the message's data.
566598
*/
567599
buf[bufpos] = *(PyBytes_AS_STRING(msg_type));
568-
msg_length = PyBytes_GET_SIZE(serialized) + 4;
600+
msg_length = PyBytes_GET_SIZE(serialized);
601+
INCMSGSIZE(msg_length, 4);
569602
msg_length = local_ntohl(msg_length);
570-
memcpy(buf + bufpos + msg_type_size, &msg_length, 4);
571-
memcpy(
603+
Py_MEMCPY(buf + bufpos + msg_type_size, &msg_length, 4);
604+
Py_MEMCPY(
572605
buf + bufpos + 4 + msg_type_size,
573606
PyBytes_AS_STRING(serialized),
574607
PyBytes_GET_SIZE(serialized)

postgresql/test/test_protocol.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def testLarge(self):
180180
]
181181

182182
class test_element3(unittest.TestCase):
183-
def test_catmessages(self):
183+
def test_cat_messages(self):
184184
# The optimized implementation will identify adjacent copy data, and
185185
# take a more efficient route; so rigorously test the switch between the
186186
# two modes.
@@ -207,6 +207,9 @@ def test_catmessages(self):
207207
self.failUnlessEqual(e3.cat_messages([(b'foo',None,b'bar'),]),
208208
b'D' + pack_head(7 + 7 + 4 + 4 + 2, 3) + \
209209
b'\x00\x00\x00\x03foo\xFF\xFF\xFF\xFF\x00\x00\x00\x03bar')
210+
# too many attributes
211+
self.failUnlessRaises((OverflowError, struct.error),
212+
e3.cat_messages, [(None,) * 0x10000])
210213

211214
class ThisEx(Exception):
212215
pass

0 commit comments

Comments
 (0)