Skip to content

Commit 412bfee

Browse files
ibrarahmadmason-sharp
authored andcommitted
Derive the lolor.node bound from the OID encoding.
A generated large object OID reserves four bits for the node id, but the GUC accepted 0..16. Node 16 does not fit and was silently encoded as node 0, so two nodes could generate colliding OIDs. Move the encoding parameters into lolor.h and compute the GUC maximum from them, so the bound cannot drift from the layout it protects.
1 parent aeb81d9 commit 412bfee

6 files changed

Lines changed: 36 additions & 9 deletions

File tree

‎docs/lolor_release_notes.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* Migration is node-local: native large objects are never replicated, so each node holds an independent set and `migrate_from_native()` migrates only the local node's objects. Run it on every node that holds native large objects, for example via `spock.replicate_ddl('SELECT lolor.migrate_from_native()')`. Migrated objects keep their original native OIDs, which are not node-encoded and can collide across nodes if different nodes hold different objects under the same OID; newly created large objects are collision-free, since new OIDs are node-encoded via `lolor.node` and checked against existing rows.
1111
* **Security fix: `lo_import()` and `lo_export()` were executable by any database user.** These read and write files on the server as the account PostgreSQL runs under, so core revokes `EXECUTE` on them from `PUBLIC`. lolor replaces them by renaming the originals to `*_orig`; an ACL belongs to a function rather than a name, so the restriction stayed on the parked original while each replacement got the default `EXECUTE TO PUBLIC`. Any user could read an arbitrary server file with `lo_import()` or overwrite one with `lo_export()`. The replacements are now locked down at install time, and upgrading revokes the privilege on existing installations in either state. Versions 1.0 through 1.2.2 are affected.
1212
* The extension is no longer marked `trusted`. Installing lolor renames functions in `pg_catalog` for the whole database, which a non-superuser should not be able to do.
13+
* Fixed the `lolor.node` upper bound. The GUC accepted 0..16 while a generated OID reserves four bits for the node id, so node 16 did not fit: the node field of every OID it generated read back as 0. The bound is now derived from the encoding (`LOLOR_MAX_NODE_ID`), giving 0..15. **If you have `lolor.node = 16` configured**, the server still starts but logs `16 is outside the valid range for parameter "lolor.node" (0 .. 15)` and falls back to 0, which is the node id it was effectively using already. Set it to a value in 0..15, and check for OID collisions against whichever node is genuinely 0.
1314
* Expanded test coverage: TAP tests for dump/restore, streaming and logical replication, and standby promotion; regression tests for `lo_lseek`, `lo_tell`, and `lo_truncate`.
1415
* Security hardening: addressed Codacy/Flawfinder warnings.
1516

‎expected/lolor.out‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,15 @@ ORDER BY 1;
721721
DROP EXTENSION lolor;
722722
NOTICE: no lolor large objects to migrate
723723
--
724+
-- lolor.node is bounded by the OID encoding, not by an independently written
725+
-- constant: the low LOLOR_NODEID_BITS of a generated OID carry the node id, so
726+
-- 16 does not fit and was previously accepted while encoding as node 0.
727+
--
728+
SET lolor.node = 16;
729+
ERROR: 16 is outside the valid range for parameter "lolor.node" (0 .. 15)
730+
SET lolor.node = 15;
731+
SET lolor.node = 1;
732+
--
724733
-- 64-bit interface and page-boundary I/O. lo_put(), lo_tell64() and
725734
-- lo_truncate64() had no coverage.
726735
--

‎sql/lolor.sql‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,15 @@ WHERE r.proname IN ('lo_import', 'lo_export')
308308
ORDER BY 1;
309309
DROP EXTENSION lolor;
310310

311+
--
312+
-- lolor.node is bounded by the OID encoding, not by an independently written
313+
-- constant: the low LOLOR_NODEID_BITS of a generated OID carry the node id, so
314+
-- 16 does not fit and was previously accepted while encoding as node 0.
315+
--
316+
SET lolor.node = 16;
317+
SET lolor.node = 15;
318+
SET lolor.node = 1;
319+
311320
--
312321
-- 64-bit interface and page-boundary I/O. lo_put(), lo_tell64() and
313322
-- lo_truncate64() had no coverage.

‎src/lolor.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ _PG_init(void)
156156
&lolor_node_id,
157157
0,
158158
0,
159-
16,
159+
LOLOR_MAX_NODE_ID,
160160
PGC_SUSET,
161161
0,
162162
NULL, NULL, NULL);

‎src/lolor.h‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@
2121
#define LOLOR_LARGEOBJECT_METADATA "pg_largeobject_metadata"
2222
#define LOLOR_LARGEOBJECT_METADATA_PKEY "pg_largeobject_metadata_pkey"
2323

24+
/*
25+
* Layout of a lolor-assigned large object OID: the low LOLOR_NODEID_BITS hold
26+
* lolor.node and the rest hold the generated OID, so that concurrent creation
27+
* on different nodes cannot collide. The GUC bound is derived from the
28+
* encoding rather than written out separately, since the two must agree.
29+
* Changing these changes the on-disk OID encoding.
30+
*/
31+
#define LOLOR_NODEID_BITS 4
32+
#define LOLOR_OID_BITS 28
33+
#define LOLOR_MAX_NODE_ID ((1 << LOLOR_NODEID_BITS) - 1)
34+
2435
/* lolor.c */
2536
extern int32 lolor_node_id;
2637
extern Oid get_LOLOR_LargeObjectRelationId(void);

‎src/lolor_largeobject.c‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@
3939
#define GETNEWOID_LOG_THRESHOLD 1000000
4040
#define GETNEWOID_LOG_MAX_INTERVAL 128000000
4141

42-
/* Parameters to determine new unique Oid. */
43-
#define MAX_NODEID_BITS 4
44-
#define MAX_OID_BITS 28
45-
4642
/*
4743
* Create a large object having the given LO identifier.
4844
*
@@ -204,8 +200,9 @@ LOLOR_LargeObjectExists(Oid loid)
204200
* LOLOR_GetNewOidWithIndex
205201
* Generate a new OID that is unique within the given relation.
206202
*
207-
* The lower 4 bits contains the lolor_node_id. The 2^28 bits consist of Oid
208-
* returned from GetNewObjectId and adjusted to remain within the range.
203+
* The low LOLOR_NODEID_BITS contain lolor_node_id; the remaining
204+
* LOLOR_OID_BITS hold an Oid returned from GetNewObjectId, adjusted to remain
205+
* within range.
209206
*
210207
* See comments for GetNewOidWithIndex() for more details.
211208
*/
@@ -236,11 +233,11 @@ LOLOR_GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
236233
* Keep the range within 1..2^28. Restart from start on overflow and see
237234
* if any of the Oids are avaialbe.
238235
*/
239-
newOid = newOid % (1 << MAX_OID_BITS);
236+
newOid = newOid % (1 << LOLOR_OID_BITS);
240237
if (newOid == 0)
241238
newOid = 1;
242239

243-
newOid = (newOid << MAX_NODEID_BITS) | lolor_node_id;
240+
newOid = (newOid << LOLOR_NODEID_BITS) | lolor_node_id;
244241

245242
if (IsBootstrapProcessingMode())
246243
return newOid;

0 commit comments

Comments
 (0)