10000 Add defenses against running with a wrong selection of LOBLKSIZE. · micdev42/postgres@39129ae · GitHub
[go: up one dir, main page]

Skip to content

Commit 39129ae

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 80d45ae commit 39129ae

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
@@ -168,13 +168,38 @@ myLargeObjectExists(Oid loid, Snapshot snapshot)
168168
}
169169

170170

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

180205

@@ -351,21 +376,15 @@ inv_getsize(LargeObjectDesc *obj_desc)
351376
{
352377
Form_pg_largeobject data;
353378
bytea *datafield;
379+
int len;
354380
bool pfreeit;
355381

356382
found = true;
357383
if (HeapTupleHasNulls(tuple)) /* paranoia */
358384
elog(ERROR, "null field found in pg_largeobject");
359385
data = (Form_pg_largeobject) GETSTRUCT(tuple);
360-
datafield = &(data->data); /* see note at top of file */
361-
pfreeit = false;
362-
if (VARATT_IS_EXTENDED(datafield))
363-
{
364-
datafield = (bytea *)
365-
heap_tuple_untoast_attr((struct varlena *) datafield);
366-
pfreeit = true;
367-
}
368-
lastbyte = data->pageno * LOBLKSIZE + getbytealen(datafield);
386+
getdatafield(data, &datafield, &len, &pfreeit);
387+
lastbyte = data->pageno * LOBLKSIZE + len;
369388
if (pfreeit)
370389
pfree(datafield);
371390
break;
@@ -485,15 +504,7 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes)
485504
off = (int) (obj_desc->offset - pageoff);
486505
Assert(off >= 0 && off < LOBLKSIZE);
487506

488-
datafield = &(data->data); /* see note at top of file */
489-
pfreeit = false;
490-
if (VARATT_IS_EXTENDED(datafield))
491-
{
492-
datafield = (bytea *)
493-
heap_tuple_untoast_attr((struct varlena *) datafield);
494-
pfreeit = true;
495-
}
496-
len = getbytealen(datafield);
507+
getdatafield(data, &datafield, &len, &pfreeit);
497508
if (len > off)
498509
{
499510
n = len - off;
@@ -606,16 +617,7 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
606617
*
607618
* First, load old data into workbuf
608619
*/
609-
datafield = &(olddata->data); /* see note at top of file */
610-
pfreeit = false;
611-
if (VARATT_IS_EXTENDED(datafield))
612-
{
613-
datafield = (bytea *)
614-
heap_tuple_untoast_attr((struct varlena *) datafield);
615-
pfreeit = true;
616-
}
617-
len = getbytealen(datafield);
618-
Assert(len <= LOBLKSIZE);
620+
getdatafield(olddata, &datafield, &len, &pfreeit);
619621
memcpy(workb, VARDATA(datafield), len);
620622
if (pfreeit)
621623
pfree(datafield);
@@ -782,19 +784,11 @@ inv_truncate(LargeObjectDesc *obj_desc, int len)
782784
if (olddata != NULL && olddata->pageno == pageno)
783785
{
784786
/* First, load old data into workbuf */
785-
bytea *datafield = &(olddata->data); /* see note at top of
786-
* file */
787-
bool pfreeit = false;
787+
bytea *datafield;
788788
int pagelen;
789+
bool pfreeit;
789790

790-
if (VARATT_IS_EXTENDED(datafield))
791-
{
792-
datafield = (bytea *)
793-
heap_tuple_untoast_attr((struct varlena *) datafield);
794-
pfreeit = true;
795-
}
796-
pagelen = getbytealen(datafield);
797-
Assert(pagelen <= LOBLKSIZE);
791+
getdatafield(olddata, &datafield, &pagelen, &pfreeit);
798792
memcpy(workb, VARDATA(datafield), pagelen);
799793
if (pfreeit)
800794
pfree(datafield);

0 commit comments

Comments
 (0)
0