Skip to content

Commit 152bfc0

Browse files
committed
Fix bugs in manipulation of large objects.
In v16 and up (since commit afbfc02), large object ownership checking has been broken because object_ownercheck() didn't take care of the discrepancy between our object-address representation of large objects (classId == LargeObjectRelationId) and the catalog where their ownership info is actually stored (LargeObjectMetadataRelationId). This resulted in failures such as "unrecognized class ID: 2613" when trying to update blob properties as a non-superuser. Poking around for related bugs, I found that AlterObjectOwner_internal would pass the wrong classId to the PostAlterHook in the no-op code path where the large object already has the desired owner. Also, recordExtObjInitPriv checked for the wrong classId; that bug is only latent because the stanza is dead code anyway, but as long as we're carrying it around it should be less wrong. These bugs are quite old. In HEAD, we can reduce the scope for future bugs of this ilk by changing AlterObjectOwner_internal's API to let the translation happen inside that function, rather than requiring callers to know about it. A more bulletproof fix, perhaps, would be to start using LargeObjectMetadataRelationId as the dependency and object-address classId for blobs. However that has substantial risk of breaking third-party code; even within our own code, it'd create hassles for pg_dump which would have to cope with a version-dependent representation. For now, keep the status quo. Discussion: https://postgr.es/m/2650449.1702497209@sss.pgh.pa.us
1 parent db69101 commit 152bfc0

File tree

8 files changed

+50
-17
lines changed

8 files changed

+50
-17
lines changed

src/backend/catalog/aclchk.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3967,9 +3967,14 @@ object_ownercheck(Oid classid, Oid objectid, Oid roleid)
39673967
if (superuser_arg(roleid))
39683968
return true;
39693969

3970+
/* For large objects, the catalog to consult is pg_largeobject_metadata */
3971+
if (classid == LargeObjectRelationId)
3972+
classid = LargeObjectMetadataRelationId;
3973+
39703974
cacheid = get_object_catcache_oid(classid);
39713975
if (cacheid != -1)
39723976
{
3977+
/* we can get the object's tuple from the syscache */
39733978
HeapTuple tuple;
39743979

39753980
tuple = SearchSysCache1(cacheid, ObjectIdGetDatum(objectid));
@@ -3986,7 +3991,6 @@ object_ownercheck(Oid classid, Oid objectid, Oid roleid)
39863991
else
39873992
{
39883993
/* for catalogs without an appropriate syscache */
3989-
39903994
Relation rel;
39913995
ScanKeyData entry[1];
39923996
SysScanDesc scan;
@@ -4306,9 +4310,9 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
43064310

43074311
ReleaseSysCache(tuple);
43084312
}
4309-
/* pg_largeobject_metadata */
4310-
else if (classoid == LargeObjectMetadataRelationId)
4313+
else if (classoid == LargeObjectRelationId)
43114314
{
4315+
/* For large objects, we must consult pg_largeobject_metadata */
43124316
Datum aclDatum;
43134317
bool isNull;
43144318
HeapTuple tuple;

src/backend/catalog/pg_shdepend.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,10 @@ shdepReassignOwned(List *roleids, Oid newrole)
16181618
Oid classId = sdepForm->classid;
16191619
Relation catalog;
16201620

1621+
/*
1622+
* For large objects, the catalog to modify is
1623+
* pg_largeobject_metadata
1624+
*/
16211625
if (classId == LargeObjectRelationId)
16221626
classId = LargeObjectMetadataRelationId;
16231627

src/backend/commands/alter.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -929,9 +929,8 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
929929
classId = address.classId;
930930

931931
/*
932-
* XXX - get_object_address returns Oid of pg_largeobject
933-
* catalog for OBJECT_LARGEOBJECT because of historical
934-
* reasons. Fix up it here.
932+
* For large objects, the catalog to modify is
933+
* pg_largeobject_metadata
935934
*/
936935
if (classId == LargeObjectRelationId)
937936
classId = LargeObjectMetadataRelationId;
@@ -1075,16 +1074,31 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
10751074
/* Perform actual update */
10761075
CatalogTupleUpdate(rel, &newtup->t_self, newtup);
10771076

1078-
/* Update owner dependency reference */
1077+
/*
1078+
* Update owner dependency reference. When working on a large object,
1079+
* we have to translate back to the OID conventionally used for LOs'
1080+
* classId.
1081+
*/
10791082
if (classId == LargeObjectMetadataRelationId)
10801083
classId = LargeObjectRelationId;
1084+
10811085
changeDependencyOnOwner(classId, objectId, new_ownerId);
10821086

10831087
/* Release memory */
10841088
pfree(values);
10851089
pfree(nulls);
10861090
pfree(replaces);
10871091
}
1092+
else
1093+
{
1094+
/*
1095+
* No need to change anything. But when working on a large object, we
1096+
* have to translate back to the OID conventionally used for LOs'
1097+
* classId, or the post-alter hook (if any) will get confused.
1098+
*/
1099+
if (classId == LargeObjectMetadataRelationId)
1100+
classId = LargeObjectRelationId;
1101+
}
10881102

10891103
InvokeObjectPostAlterHook(classId, objectId, 0);
10901104
}

src/backend/libpq/be-fsstubs.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
#include <unistd.h>
4444

4545
#include "access/xact.h"
46-
#include "catalog/pg_largeobject_metadata.h"
46+
#include "catalog/pg_largeobject.h"
4747
#include "libpq/be-fsstubs.h"
4848
#include "libpq/libpq-fs.h"
4949
#include "miscadmin.h"
@@ -323,7 +323,7 @@ be_lo_unlink(PG_FUNCTION_ARGS)
323323
* relevant FDs.
324324
*/
325325
if (!lo_compat_privileges &&
326-
!object_ownercheck(LargeObjectMetadataRelationId, lobjId, GetUserId()))
326+
!object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
327327
ereport(ERROR,
328328
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
329329
errmsg("must be owner of large object %u", lobjId)));

src/backend/storage/large_object/inv_api.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,10 @@ inv_create(Oid lobjId)
221221
/*
222222
* dependency on the owner of largeobject
223223
*
224-
* The reason why we use LargeObjectRelationId instead of
225-
* LargeObjectMetadataRelationId here is to provide backward compatibility
226-
* to the applications which utilize a knowledge about internal layout of
227-
* system catalogs. OID of pg_largeobject_metadata and loid of
228-
* pg_largeobject are same value, so there are no actual differences here.
224+
* Note that LO dependencies are recorded using classId
225+
* LargeObjectRelationId for backwards-compatibility reasons. Using
226+
* LargeObjectMetadataRelationId instead would simplify matters for the
227+
* backend, but it'd complicate pg_dump and possibly break other clients.
229228
*/
230229
recordDependencyOnOwner(LargeObjectRelationId,
231230
lobjId_new, GetUserId());

src/test/regress/expected/largeobject.out

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
\getenv abs_builddir PG_ABS_BUILDDIR
77
-- ensure consistent test output regardless of the default bytea format
88
SET bytea_output TO escape;
9-
-- Test ALTER LARGE OBJECT OWNER, GRANT, COMMENT
9+
-- Test ALTER LARGE OBJECT OWNER
1010
CREATE ROLE regress_lo_user;
1111
SELECT lo_create(42);
1212
lo_create
@@ -15,8 +15,11 @@ SELECT lo_create(42);
1515
(1 row)
1616

1717
ALTER LARGE OBJECT 42 OWNER TO regress_lo_user;
18+
-- Test GRANT, COMMENT as non-superuser
19+
SET SESSION AUTHORIZATION regress_lo_user;
1820
GRANT SELECT ON LARGE OBJECT 42 TO public;
1921
COMMENT ON LARGE OBJECT 42 IS 'the ultimate answer';
22+
RESET SESSION AUTHORIZATION;
2023
-- Test psql's \lo_list et al (we assume no other LOs exist yet)
2124
\lo_list
2225
Large objects

src/test/regress/expected/largeobject_1.out

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
\getenv abs_builddir PG_ABS_BUILDDIR
77
-- ensure consistent test output regardless of the default bytea format
88
SET bytea_output TO escape;
9-
-- Test ALTER LARGE OBJECT OWNER, GRANT, COMMENT
9+
-- Test ALTER LARGE OBJECT OWNER
1010
CREATE ROLE regress_lo_user;
1111
SELECT lo_create(42);
1212
lo_create
@@ -15,8 +15,11 @@ SELECT lo_create(42);
1515
(1 row)
1616

1717
ALTER LARGE OBJECT 42 OWNER TO regress_lo_user;
18+
-- Test GRANT, COMMENT as non-superuser
19+
SET SESSION AUTHORIZATION regress_lo_user;
1820
GRANT SELECT ON LARGE OBJECT 42 TO public;
1921
COMMENT ON LARGE OBJECT 42 IS 'the ultimate answer';
22+
RESET SESSION AUTHORIZATION;
2023
-- Test psql's \lo_list et al (we assume no other LOs exist yet)
2124
\lo_list
2225
Large objects

src/test/regress/sql/largeobject.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99
-- ensure consistent test output regardless of the default bytea format
1010
SET bytea_output TO escape;
1111

12-
-- Test ALTER LARGE OBJECT OWNER, GRANT, COMMENT
12+
-- Test ALTER LARGE OBJECT OWNER
1313
CREATE ROLE regress_lo_user;
1414
SELECT lo_create(42);
1515
ALTER LARGE OBJECT 42 OWNER TO regress_lo_user;
16+
17+
-- Test GRANT, COMMENT as non-superuser
18+
SET SESSION AUTHORIZATION regress_lo_user;
19+
1620
GRANT SELECT ON LARGE OBJECT 42 TO public;
1721
COMMENT ON LARGE OBJECT 42 IS 'the ultimate answer';
1822

23+
RESET SESSION AUTHORIZATION;
24+
1925
-- Test psql's \lo_list et al (we assume no other LOs exist yet)
2026
\lo_list
2127
\lo_list+

0 commit comments

Comments
 (0)