Skip to content
Snippets Groups Projects
Commit 4fb64782 authored by Tom Lane's avatar Tom Lane
Browse files

Add defenses against running with a wrong selection of LOBLKSIZE.

It's critical that the backend's idea of LOBLKSIZE match the way data has
actually been divided up in pg_largeobject.  While we don't provide any
direct way to adjust that value, doing so is a one-line source code change
and various people have expressed interest recently in changing it.  So,
just as with TOAST_MAX_CHUNK_SIZE, it seems prudent to record the value in
pg_control and cross-check that the backend's compiled-in setting matches
the on-disk data.

Also tweak the code in inv_api.c so that fetches from pg_largeobject
explicitly verify that the length of the data field is not more than
LOBLKSIZE.  Formerly we just had Asserts() for that, which is no protection
at all in production builds.  In some of the call sites an overlength data
value would translate directly to a security-relevant stack clobber, so it
seems worth one extra runtime comparison to be sure.

In the back branches, we can't change the contents of pg_control; but we
can still make the extra checks in inv_api.c, which will offer some amount
of protection against running with the wrong value of LOBLKSIZE.
parent 315442c0
No related branches found
No related tags found
No related merge requests found
...@@ -171,13 +171,38 @@ myLargeObjectExists(Oid loid, Snapshot snapshot) ...@@ -171,13 +171,38 @@ myLargeObjectExists(Oid loid, Snapshot snapshot)
} }
static int32 /*
getbytealen(bytea *data) * Extract data field from a pg_largeobject tuple, detoasting if needed
* and verifying that the length is sane. Returns data pointer (a bytea *),
* data length, and an indication of whether to pfree the data pointer.
*/
static void
getdatafield(Form_pg_largeobject tuple,
bytea **pdatafield,
int *plen,
bool *pfreeit)
{ {
Assert(!VARATT_IS_EXTENDED(data)); bytea *datafield;
if (VARSIZE(data) < VARHDRSZ) int len;
elog(ERROR, "invalid VARSIZE(data)"); bool freeit;
return (VARSIZE(data) - VARHDRSZ);
datafield = &(tuple->data); /* see note at top of file */
freeit = false;
if (VARATT_IS_EXTENDED(datafield))
{
datafield = (bytea *)
heap_tuple_untoast_attr((struct varlena *) datafield);
freeit = true;
}
len = VARSIZE(datafield) - VARHDRSZ;
if (len < 0 || len > LOBLKSIZE)
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("pg_largeobject entry for OID %u, page %d has invalid data field size %d",
tuple->loid, tuple->pageno, len)));
*pdatafield = datafield;
*plen = len;
*pfreeit = freeit;
} }
...@@ -363,20 +388,14 @@ inv_getsize(LargeObjectDesc *obj_desc) ...@@ -363,20 +388,14 @@ inv_getsize(LargeObjectDesc *obj_desc)
{ {
Form_pg_largeobject data; Form_pg_largeobject data;
bytea *datafield; bytea *datafield;
int len;
bool pfreeit; bool pfreeit;
if (HeapTupleHasNulls(tuple)) /* paranoia */ if (HeapTupleHasNulls(tuple)) /* paranoia */
elog(ERROR, "null field found in pg_largeobject"); elog(ERROR, "null field found in pg_largeobject");
data = (Form_pg_largeobject) GETSTRUCT(tuple); data = (Form_pg_largeobject) GETSTRUCT(tuple);
datafield = &(data->data); /* see note at top of file */ getdatafield(data, &datafield, &len, &pfreeit);
pfreeit = false; lastbyte = data->pageno * LOBLKSIZE + len;
if (VARATT_IS_EXTENDED(datafield))
{
datafield = (bytea *)
heap_tuple_untoast_attr((struct varlena *) datafield);
pfreeit = true;
}
lastbyte = data->pageno * LOBLKSIZE + getbytealen(datafield);
if (pfreeit) if (pfreeit)
pfree(datafield); pfree(datafield);
} }
...@@ -491,15 +510,7 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes) ...@@ -491,15 +510,7 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes)
off = (int) (obj_desc->offset - pageoff); off = (int) (obj_desc->offset - pageoff);
Assert(off >= 0 && off < LOBLKSIZE); Assert(off >= 0 && off < LOBLKSIZE);
datafield = &(data->data); /* see note at top of file */ getdatafield(data, &datafield, &len, &pfreeit);
pfreeit = false;
if (VARATT_IS_EXTENDED(datafield))
{
datafield = (bytea *)
heap_tuple_untoast_attr((struct varlena *) datafield);
pfreeit = true;
}
len = getbytealen(datafield);
if (len > off) if (len > off)
{ {
n = len - off; n = len - off;
...@@ -618,16 +629,7 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes) ...@@ -618,16 +629,7 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
* *
* First, load old data into workbuf * First, load old data into workbuf
*/ */
datafield = &(olddata->data); /* see note at top of file */ getdatafield(olddata, &datafield, &len, &pfreeit);
pfreeit = false;
if (VARATT_IS_EXTENDED(datafield))
{
datafield = (bytea *)
heap_tuple_untoast_attr((struct varlena *) datafield);
pfreeit = true;
}
len = getbytealen(datafield);
Assert(len <= LOBLKSIZE);
memcpy(workb, VARDATA(datafield), len); memcpy(workb, VARDATA(datafield), len);
if (pfreeit) if (pfreeit)
pfree(datafield); pfree(datafield);
...@@ -803,19 +805,11 @@ inv_truncate(LargeObjectDesc *obj_desc, int len) ...@@ -803,19 +805,11 @@ inv_truncate(LargeObjectDesc *obj_desc, int len)
if (olddata != NULL && olddata->pageno == pageno) if (olddata != NULL && olddata->pageno == pageno)
{ {
/* First, load old data into workbuf */ /* First, load old data into workbuf */
bytea *datafield = &(olddata->data); /* see note at top of bytea *datafield;
* file */
bool pfreeit = false;
int pagelen; int pagelen;
bool pfreeit;
if (VARATT_IS_EXTENDED(datafield)) getdatafield(olddata, &datafield, &pagelen, &pfreeit);
{
datafield = (bytea *)
heap_tuple_untoast_attr((struct varlena *) datafield);
pfreeit = true;
}
pagelen = getbytealen(datafield);
Assert(pagelen <= LOBLKSIZE);
memcpy(workb, VARDATA(datafield), pagelen); memcpy(workb, VARDATA(datafield), pagelen);
if (pfreeit) if (pfreeit)
pfree(datafield); pfree(datafield);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment