From dc095f6bbc31045d62a515aebd5e349bd341845a Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Thu, 25 Sep 2025 21:40:18 +0200 Subject: [PATCH v2 1/5] Support BYPASSLEAKPROOF flag for policies --- src/backend/catalog/system_views.sql | 3 ++- src/backend/commands/policy.c | 8 +++++++ src/backend/parser/gram.y | 35 +++++++++++++++++++++++++++- src/bin/pg_dump/pg_dump.c | 11 +++++++++ src/bin/pg_dump/pg_dump.h | 1 + src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_policy.h | 2 ++ src/include/nodes/parsenodes.h | 10 ++++++++ src/include/parser/kwlist.h | 2 ++ 9 files changed, 71 insertions(+), 3 deletions(-) diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index c77fa0234bb..7122e9c2ae4 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -100,7 +100,8 @@ CREATE VIEW pg_policies AS WHEN '*' THEN 'ALL' END AS cmd, pg_catalog.pg_get_expr(pol.polqual, pol.polrelid) AS qual, - pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid) AS with_check + pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid) AS with_check, + pol.polbypassleakproof AS bypassleakproof FROM pg_catalog.pg_policy pol JOIN pg_catalog.pg_class C ON (C.oid = pol.polrelid) LEFT JOIN pg_catalog.pg_namespace N ON (N.oid = C.relnamespace); diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c index 83056960fe4..a5b2a9dfc87 100644 --- a/src/backend/commands/policy.c +++ b/src/backend/commands/policy.c @@ -694,6 +694,7 @@ CreatePolicy(CreatePolicyStmt *stmt) CStringGetDatum(stmt->policy_name)); values[Anum_pg_policy_polcmd - 1] = CharGetDatum(polcmd); values[Anum_pg_policy_polpermissive - 1] = BoolGetDatum(stmt->permissive); + values[Anum_pg_policy_polbypassleakproof - 1] = BoolGetDatum(stmt->bypassleakproof); values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); /* Add qual if present. */ @@ -1036,6 +1037,13 @@ AlterPolicy(AlterPolicyStmt *stmt) } } + /* Only update bypassleakproof if the clause was specified */ + if (stmt->bypassleakproof_given) + { + replaces[Anum_pg_policy_polbypassleakproof - 1] = true; + values[Anum_pg_policy_polbypassleakproof - 1] = BoolGetDatum(stmt->bypassleakproof); + } + new_tuple = heap_modify_tuple(policy_tuple, RelationGetDescr(pg_policy_rel), values, isnull, replaces); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9fd48acb1f8..7de026c0e16 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -268,6 +268,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct KeyAction *keyaction; ReturningClause *retclause; ReturningOptionKind retoptionkind; + struct { bool given; bool val; } bypassopt; } %type stmt toplevel_stmt schema_stmt routine_body_stmt @@ -395,6 +396,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type RowSecurityDefaultPermissive %type RowSecurityOptionalWithCheck RowSecurityOptionalExpr %type RowSecurityDefaultToRole RowSecurityOptionalToRole +%type RowSecurityOptionalBypassleakproof %type iso_level opt_encoding %type grantee @@ -702,7 +704,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT - BOOLEAN_P BOTH BREADTH BY + BOOLEAN_P BOTH BREADTH BY BYPASSLEAKPROOF CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE @@ -747,6 +749,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO + NOBYPASSLEAKPROOF NONE NORMALIZE NORMALIZED NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC @@ -5929,8 +5932,10 @@ AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generi * [FOR { SELECT | INSERT | UPDATE | DELETE } ] * [TO role, ...] * [USING (qual)] [WITH CHECK (with check qual)] + * [{ BYPASSLEAKPROOF | NOBYPASSLEAKPROOF }] * ALTER POLICY name ON table [TO role, ...] * [USING (qual)] [WITH CHECK (with check qual)] + * [{ BYPASSLEAKPROOF | NOBYPASSLEAKPROOF }] * *****************************************************************************/ @@ -5938,6 +5943,7 @@ CreatePolicyStmt: CREATE POLICY name ON qualified_name RowSecurityDefaultPermissive RowSecurityDefaultForCmd RowSecurityDefaultToRole RowSecurityOptionalExpr RowSecurityOptionalWithCheck + RowSecurityOptionalBypassleakproof { CreatePolicyStmt *n = makeNode(CreatePolicyStmt); @@ -5948,6 +5954,8 @@ CreatePolicyStmt: n->roles = $8; n->qual = $9; n->with_check = $10; + n->bypassleakproof = $11.val; + n->bypassleakproof_given = $11.given; $$ = (Node *) n; } ; @@ -5955,6 +5963,7 @@ CreatePolicyStmt: AlterPolicyStmt: ALTER POLICY name ON qualified_name RowSecurityOptionalToRole RowSecurityOptionalExpr RowSecurityOptionalWithCheck + RowSecurityOptionalBypassleakproof { AlterPolicyStmt *n = makeNode(AlterPolicyStmt); @@ -5963,6 +5972,8 @@ AlterPolicyStmt: n->roles = $6; n->qual = $7; n->with_check = $8; + n->bypassleakproof = $9.val; + n->bypassleakproof_given = $9.given; $$ = (Node *) n; } ; @@ -6005,6 +6016,24 @@ RowSecurityDefaultPermissive: | /* EMPTY */ { $$ = true; } ; +RowSecurityOptionalBypassleakproof: + BYPASSLEAKPROOF + { + $$.given = true; + $$.val = true; + } + | NOBYPASSLEAKPROOF + { + $$.given = true; + $$.val = false; + } + | /* EMPTY */ + { + $$.given = false; + $$.val = false; + } + ; + RowSecurityDefaultForCmd: FOR row_security_cmd { $$ = $2; } | /* EMPTY */ { $$ = "all"; } @@ -17755,6 +17784,7 @@ unreserved_keyword: | BEGIN_P | BREADTH | BY + | BYPASSLEAKPROOF | CACHE | CALL | CALLED @@ -17904,6 +17934,7 @@ unreserved_keyword: | NFKC | NFKD | NO + | NOBYPASSLEAKPROOF | NORMALIZED | NOTHING | NOTIFY @@ -18310,6 +18341,7 @@ bare_label_keyword: | BOTH | BREADTH | BY + | BYPASSLEAKPROOF | CACHE | CALL | CALLED @@ -18521,6 +18553,7 @@ bare_label_keyword: | NFKC | NFKD | NO + | NOBYPASSLEAKPROOF | NONE | NORMALIZE | NORMALIZED diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 9fc3671cb35..f4a1df3af85 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4243,6 +4243,7 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) int i_polname; int i_polcmd; int i_polpermissive; + int i_polbypassleakproof; int i_polroles; int i_polqual; int i_polwithcheck; @@ -4306,6 +4307,7 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) polinfo->polname = NULL; polinfo->polcmd = '\0'; polinfo->polpermissive = 0; + polinfo->polbypassleakproof = 0; polinfo->polroles = NULL; polinfo->polqual = NULL; polinfo->polwithcheck = NULL; @@ -4327,6 +4329,10 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) appendPQExpBufferStr(query, "pol.polpermissive, "); else appendPQExpBufferStr(query, "'t' as polpermissive, "); + if (fout->remoteVersion >= 190000) /* polbypassleakproof added in v19 */ + appendPQExpBufferStr(query, "pol.polbypassleakproof, "); + else + appendPQExpBufferStr(query, "'f' as polbypassleakproof, "); appendPQExpBuffer(query, "CASE WHEN pol.polroles = '{0}' THEN NULL ELSE " " pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(rolname) from pg_catalog.pg_roles WHERE oid = ANY(pol.polroles)), ', ') END AS polroles, " @@ -4347,6 +4353,7 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) i_polname = PQfnumber(res, "polname"); i_polcmd = PQfnumber(res, "polcmd"); i_polpermissive = PQfnumber(res, "polpermissive"); + i_polbypassleakproof = PQfnumber(res, "polbypassleakproof"); i_polroles = PQfnumber(res, "polroles"); i_polqual = PQfnumber(res, "polqual"); i_polwithcheck = PQfnumber(res, "polwithcheck"); @@ -4372,6 +4379,7 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) polinfo[j].polcmd = *(PQgetvalue(res, j, i_polcmd)); polinfo[j].polpermissive = *(PQgetvalue(res, j, i_polpermissive)) == 't'; + polinfo[j].polbypassleakproof = *(PQgetvalue(res, j, i_polbypassleakproof)) == 't'; if (PQgetisnull(res, j, i_polroles)) polinfo[j].polroles = NULL; @@ -4483,6 +4491,9 @@ dumpPolicy(Archive *fout, const PolicyInfo *polinfo) if (polinfo->polwithcheck != NULL) appendPQExpBuffer(query, " WITH CHECK (%s)", polinfo->polwithcheck); + if (polinfo->polbypassleakproof) + appendPQExpBufferStr(query, " BYPASSLEAKPROOF"); + appendPQExpBufferStr(query, ";\n"); appendPQExpBuffer(delqry, "DROP POLICY %s", fmtId(polinfo->polname)); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index bcc94ff07cc..7d616044913 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -656,6 +656,7 @@ typedef struct _policyInfo char *polname; /* null indicates RLS is enabled on rel */ char polcmd; bool polpermissive; + bool polbypassleakproof; char *polroles; char *polqual; char *polwithcheck; diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 62c21d3670d..6564e7ab6e1 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202509191 +#define CATALOG_VERSION_NO 202509241 #endif diff --git a/src/include/catalog/pg_policy.h b/src/include/catalog/pg_policy.h index 3c2498cdf11..2b202c18973 100644 --- a/src/include/catalog/pg_policy.h +++ b/src/include/catalog/pg_policy.h @@ -34,6 +34,8 @@ CATALOG(pg_policy,3256,PolicyRelationId) * policy. */ char polcmd; /* One of ACL_*_CHR, or '*' for all */ bool polpermissive; /* restrictive or permissive policy */ + bool polbypassleakproof; /* does the policy bypass the leakproof + * requirement for functions? */ #ifdef CATALOG_VARLEN /* Roles to which the policy is applied; zero means PUBLIC */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4ed14fc5b78..d4a56b0ac3a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3068,6 +3068,11 @@ typedef struct CreatePolicyStmt List *roles; /* the roles associated with the policy */ Node *qual; /* the policy's condition */ Node *with_check; /* the policy's WITH CHECK condition. */ + bool bypassleakproof; /* does the policy bypass the leakproof + * requirement for functions? */ + bool bypassleakproof_given; /* whether + * BYPASSLEAKPROOF/NOBYPASSLEAKPROOF + * appeared */ } CreatePolicyStmt; /*---------------------- @@ -3082,6 +3087,11 @@ typedef struct AlterPolicyStmt List *roles; /* the roles associated with the policy */ Node *qual; /* the policy's condition */ Node *with_check; /* the policy's WITH CHECK condition. */ + bool bypassleakproof; /* does the policy bypass the leakproof + * requirement for functions? */ + bool bypassleakproof_given; /* whether + * BYPASSLEAKPROOF/NOBYPASSLEAKPROOF + * appeared */ } AlterPolicyStmt; /*---------------------- diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index a4af3f717a1..65248b076bf 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -65,6 +65,7 @@ PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("both", BOTH, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("breadth", BREADTH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("by", BY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("bypassleakproof", BYPASSLEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("cache", CACHE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("call", CALL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("called", CALLED, UNRESERVED_KEYWORD, BARE_LABEL) @@ -295,6 +296,7 @@ PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("no", NO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nobypassleakproof", NOBYPASSLEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("none", NONE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD, BARE_LABEL) -- 2.50.1 (Apple Git-155)