Skip to content

Commit 780852e

Browse files
committed
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.
1 parent edde59d commit 780852e

File tree

1 file changed

+39
-45
lines changed

1 file changed

+39
-45
lines changed

src/backend/storage/large_object/inv_api.c

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,38 @@ myLargeObjectExists(Oid loid, Snapshot snapshot)
173173
}
174174

175175

176-
static int32
177-
getbytealen(bytea *data)
176+
/*
177+
* Extract data field from a pg_largeobject tuple, detoasting if needed
178+
* and verifying that the length is sane. Returns data pointer (a bytea *),
179+
* data length, and an indication of whether to pfree the data pointer.
180+
*/
181+
static void
182+
getdatafield(Form_pg_largeobject tuple,
183+
bytea **pdatafield,
184+
int *plen,
185+
bool *pfreeit)
178186
{
179-
Assert(!VARATT_IS_EXTENDED(data));
180-
if (VARSIZE(data) < VARHDRSZ)
181-
elog(ERROR, "invalid VARSIZE(data)");
182-
return (VARSIZE(data) - VARHDRSZ);
187+
bytea *datafield;
188+
int len;
189+
bool freeit;
190+
191+
datafield = &(tuple->data); /* see note at top of file */
192+
freeit = false;
193+
if (VARATT_IS_EXTENDED(datafield))
194+
{
195+
datafield = (bytea *)
196+
heap_tuple_untoast_attr((struct varlena *) datafield);
197+
freeit = true;
198+
}
199+
len = VARSIZE(datafield) - VARHDRSZ;
200+
if (len < 0 || len > LOBLKSIZE)
201+
ereport(ERROR,
202+
(errcode(ERRCODE_DATA_CORRUPTED),
203+
errmsg("pg_largeobject entry for OID %u, page %d has invalid data field size %d",
204+
tuple->loid, tuple->pageno, len)));
205+
*pdatafield = datafield;
206+
*plen = len;
207+
*pfreeit = freeit;
183208
}
184209

185210

@@ -367,20 +392,14 @@ inv_getsize(LargeObjectDesc *obj_desc)
367392
{
368393
Form_pg_largeobject data;
369394
bytea *datafield;
395+
int len;
370396
bool pfreeit;
371397

372398
if (HeapTupleHasNulls(tuple)) /* paranoia */
373399
elog(ERROR, "null field found in pg_largeobject");
374400
data = (Form_pg_largeobject) GETSTRUCT(tuple);
375-
datafield = &(data->data); /* see note at top of file */
376-
pfreeit = false;
377-
if (VARATT_IS_EXTENDED(datafield))
378-
{
379-
datafield = (bytea *)
380-
heap_tuple_untoast_attr((struct varlena *) datafield);
381-
pfreeit = true;
382-
}
383-
lastbyte = (uint64) data->pageno * LOBLKSIZE + getbytealen(datafield);
401+
getdatafield(data, &datafield, &len, &pfreeit);
402+
lastbyte = (uint64) data->pageno * LOBLKSIZE + len;
384403
if (pfreeit)
385404
pfree(datafield);
386405
}
@@ -507,15 +526,7 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes)
507526
off = (int) (obj_desc->offset - pageoff);
508527
Assert(off >= 0 && off < LOBLKSIZE);
509528

510-
datafield = &(data->data); /* see note at top of file */
511-
pfreeit = false;
512-
if (VARATT_IS_EXTENDED(datafield))
513-
{
514-
datafield = (bytea *)
515-
heap_tuple_untoast_attr((struct varlena *) datafield);
516-
pfreeit = true;
517-
}
518-
len = getbytealen(datafield);
529+
getdatafield(data, &datafield, &len, &pfreeit);
519530
if (len > off)
520531
{
521532
n = len - off;
@@ -631,16 +642,7 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
631642
*
632643
* First, load old data into workbuf
633644
*/
634-
datafield = &(olddata->data); /* see note at top of file */
635-
pfreeit = false;
636-
if (VARATT_IS_EXTENDED(datafield))
637-
{
638-
datafield = (bytea *)
639-
heap_tuple_untoast_attr((struct varlena *) datafield);
640-
pfreeit = true;
641-
}
642-
len = getbytealen(datafield);
643-
Assert(len <= LOBLKSIZE);
645+
getdatafield(olddata, &datafield, &len, &pfreeit);
644646
memcpy(workb, VARDATA(datafield), len);
645647
if (pfreeit)
646648
pfree(datafield);
@@ -816,19 +818,11 @@ inv_truncate(LargeObjectDesc *obj_desc, int64 len)
816818
if (olddata != NULL && olddata->pageno == pageno)
817819
{
818820
/* First, load old data into workbuf */
819-
bytea *datafield = &(olddata->data); /* see note at top of
820-
* file */
821-
bool pfreeit = false;
821+
bytea *datafield;
822822
int pagelen;
823+
bool pfreeit;
823824

824-
if (VARATT_IS_EXTENDED(datafield))
825-
{
826-
datafield = (bytea *)
827-
heap_tuple_untoast_attr((struct varlena *) datafield);
828-
pfreeit = true;
829-
}
830-
pagelen = getbytealen(datafield);
831-
Assert(pagelen <= LOBLKSIZE);
825+
getdatafield(olddata, &datafield, &pagelen, &pfreeit);
832826
memcpy(workb, VARDATA(datafield), pagelen);
833827
if (pfreeit)
834828
pfree(datafield);

0 commit comments

Comments
 (0)