diff options
Diffstat (limited to 'src/backend/storage/large_object/inv_api.c')
-rw-r--r-- | src/backend/storage/large_object/inv_api.c | 108 |
1 files changed, 85 insertions, 23 deletions
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c index aa43b46c305..12940e5075e 100644 --- a/src/backend/storage/large_object/inv_api.c +++ b/src/backend/storage/large_object/inv_api.c @@ -52,6 +52,11 @@ /* + * GUC: backwards-compatibility flag to suppress LO permission checks + */ +bool lo_compat_privileges; + +/* * All accesses to pg_largeobject and its index make use of a single Relation * reference, so that we only need to open pg_relation once per transaction. * To avoid problems when the first such reference occurs inside a @@ -250,46 +255,78 @@ inv_open(Oid lobjId, int flags, MemoryContext mcxt) Snapshot snapshot = NULL; int descflags = 0; + /* + * Historically, no difference is made between (INV_WRITE) and (INV_WRITE + * | INV_READ), the caller being allowed to read the large object + * descriptor in either case. + */ if (flags & INV_WRITE) - { - snapshot = NULL; /* instantaneous MVCC snapshot */ - descflags = IFS_WRLOCK | IFS_RDLOCK; - } - else if (flags & INV_READ) - { - snapshot = GetActiveSnapshot(); - descflags = IFS_RDLOCK; - } - else + descflags |= IFS_WRLOCK | IFS_RDLOCK; + if (flags & INV_READ) + descflags |= IFS_RDLOCK; + + if (descflags == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid flags for opening a large object: %d", flags))); + /* Get snapshot. If write is requested, use an instantaneous snapshot. */ + if (descflags & IFS_WRLOCK) + snapshot = NULL; + else + snapshot = GetActiveSnapshot(); + /* Can't use LargeObjectExists here because we need to specify snapshot */ if (!myLargeObjectExists(lobjId, snapshot)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("large object %u does not exist", lobjId))); + /* Apply permission checks, again specifying snapshot */ + if ((descflags & IFS_RDLOCK) != 0) + { + if (!lo_compat_privileges && + pg_largeobject_aclcheck_snapshot(lobjId, + GetUserId(), + ACL_SELECT, + snapshot) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + lobjId))); + } + if ((descflags & IFS_WRLOCK) != 0) + { + if (!lo_compat_privileges && + pg_largeobject_aclcheck_snapshot(lobjId, + GetUserId(), + ACL_UPDATE, + snapshot) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + lobjId))); + } + + /* OK to create a descriptor */ + retval = (LargeObjectDesc *) MemoryContextAlloc(mcxt, + sizeof(LargeObjectDesc)); + retval->id = lobjId; + retval->subid = GetCurrentSubTransactionId(); + retval->offset = 0; + retval->flags = descflags; + /* * We must register the snapshot in TopTransaction's resowner, because it * must stay alive until the LO is closed rather than until the current - * portal shuts down. Do this after checking that the LO exists, to avoid - * leaking the snapshot if an error is thrown. + * portal shuts down. Do this last to avoid uselessly leaking the + * snapshot if an error is thrown above. */ if (snapshot) snapshot = RegisterSnapshotOnOwner(snapshot, TopTransactionResourceOwner); - - /* All set, create a descriptor */ - retval = (LargeObjectDesc *) MemoryContextAlloc(mcxt, - sizeof(LargeObjectDesc)); - retval->id = lobjId; - retval->subid = GetCurrentSubTransactionId(); - retval->offset = 0; retval->snapshot = snapshot; - retval->flags = descflags; return retval; } @@ -312,7 +349,7 @@ inv_close(LargeObjectDesc *obj_desc) /* * Destroys an existing large object (not to be confused with a descriptor!) * - * returns -1 if failed + * Note we expect caller to have done any required permissions check. */ int inv_drop(Oid lobjId) @@ -333,6 +370,7 @@ inv_drop(Oid lobjId) */ CommandCounterIncrement(); + /* For historical reasons, we always return 1 on success. */ return 1; } @@ -398,6 +436,11 @@ inv_seek(LargeObjectDesc *obj_desc, int64 offset, int whence) Assert(PointerIsValid(obj_desc)); /* + * We allow seek/tell if you have either read or write permission, so no + * need for a permission check here. + */ + + /* * Note: overflow in the additions is possible, but since we will reject * negative results, we don't need any extra test for that. */ @@ -439,6 +482,11 @@ inv_tell(LargeObjectDesc *obj_desc) { Assert(PointerIsValid(obj_desc)); + /* + * We allow seek/tell if you have either read or write permission, so no + * need for a permission check here. + */ + return obj_desc->offset; } @@ -458,6 +506,12 @@ inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes) Assert(PointerIsValid(obj_desc)); Assert(buf != NULL); + if ((obj_desc->flags & IFS_RDLOCK) == 0) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + obj_desc->id))); + if (nbytes <= 0) return 0; @@ -563,7 +617,11 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes) Assert(buf != NULL); /* enforce writability because snapshot is probably wrong otherwise */ - Assert(obj_desc->flags & IFS_WRLOCK); + if ((obj_desc->flags & IFS_WRLOCK) == 0) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + obj_desc->id))); if (nbytes <= 0) return 0; @@ -749,7 +807,11 @@ inv_truncate(LargeObjectDesc *obj_desc, int64 len) Assert(PointerIsValid(obj_desc)); /* enforce writability because snapshot is probably wrong otherwise */ - Assert(obj_desc->flags & IFS_WRLOCK); + if ((obj_desc->flags & IFS_WRLOCK) == 0) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + obj_desc->id))); /* * use errmsg_internal here because we don't want to expose INT64_FORMAT |