普通视图无法阻止侧信道攻击,只有显式创建 security_barrier = true 视图且所有依赖函数标记为 LEAKPROOF,才能阻断谓词下推和中间结果暴露。

普通视图无法阻止侧信道攻击——攻击者可通过自定义低成本函数(如 RAISE NOTICE)触发提前执行,从而泄露被 WHERE 过滤的行数据;只有显式启用 security_barrier = true 并确保所有依赖函数为 LEAKPROOF,才能真正阻断谓词下推和中间结果暴露。
创建 security_barrier 视图必须显式声明 WITH (security_barrier = true)
PostgreSQL 默认不开启安全屏障,即使视图定义含 WHERE groupid = 2,查询时仍可能把外部条件(如 attack(id, info))下推到视图内部执行,导致未授权数据被函数“打印”出来。
正确写法:
CREATE VIEW v_userinfo WITH (security_barrier = true) AS SELECT * FROM userinfo WHERE groupid = 2;
错误写法(无声明,等同于普通视图):
CREATE VIEW v_userinfo AS SELECT * FROM userinfo WHERE groupid = 2;
验证是否生效:
- 运行
\d+ v_userinfo,输出中必须出现Security barrier: yes - 或查系统表:
SELECT definition FROM pg_views WHERE viewname = 'v_userinfo';,确认定义未被隐式重写
所有调用的函数必须标记为 LEAKPROOF
若视图里用了 to_tsvector('english', body) 或你写的 mask_phone(),而它没设 LEAKPROOF,PostgreSQL 会拒绝在 security_barrier 视图中使用它——轻则报错,重则静默降级为普通视图,失去防护能力。
检查方式:
SELECT proname, proleakproof FROM pg_proc WHERE proname IN ('to_tsvector', 'mask_phone');若 proleakproof 为 false,需重建函数(仅超级用户可操作):
CREATE OR REPLACE FUNCTION mask_phone(text) RETURNS text AS $$
SELECT regexp_replace($1, '(\d{3})\d{4}(\d{4})', '\1****\2');
$$ LANGUAGE sql LEAKPROOF;注意:
- 内置函数如
upper()、length()通常是LEAKPROOF;但to_tsvector()、jsonb_path_match()等默认不是 -
LEAKPROOF函数会强制先执行,不管成本高低——如果函数实际很慢,会影响整体性能 - 含
RAISE NOTICE、PERFORM pg_sleep()或访问外部资源的函数,绝不能标为LEAKPROOF
避免与 RLS 或 WITH CHECK OPTION 混用
security_barrier 和 WITH CHECK OPTION 互斥:PostgreSQL 明确禁止同时使用,建视图时会直接报错。
与 RLS(行级安全)共存时更需谨慎:
- 若底层表已启 RLS,且视图 owner 有足够权限,RLS 策略会在视图过滤前执行,可能提前截断数据——但这是预期行为
- 若视图设为
security_invoker = on,RLS 按调用者身份检查,此时security_barrier仍有效,前提是所有函数都LEAKPROOF - 最危险场景:RLS + 普通视图 + 非
LEAKPROOF函数 —— 攻击者可用attack()绕过 RLS 和视图双重过滤
测试时务必用无权限的普通用户连接,而非 postgres 或视图 owner 账户。否则你看不到真实防护效果。
用 EXPLAIN (VERBOSE) 确认执行计划未被干扰
安全屏障是否起作用,最终得看执行计划。加 VERBOSE 可看到谓词是否被“锁死”在视图内:
EXPLAIN (VERBOSE) SELECT * FROM v_userinfo WHERE attack(id, info);
理想输出应显示:
-
Filter: (userinfo.groupid = 2)出现在最内层扫描节点 -
attack(id, info)出现在外层 Filter,且不与userinfo.*字段混在同一节点 - 没有
Row Security Policy提前介入或谓词被重排的提示
若发现 attack() 出现在 Seq Scan on userinfo 节点内部,说明屏障失效——要么没加 security_barrier = true,要么某个函数漏标 LEAKPROOF。
真正难的不是加那行 WITH (security_barrier = true),而是整条依赖链上每个函数都得是 LEAKPROOF,且你得用非特权账户反复验证执行计划——漏掉任意一环,视图就只是个心理安慰。

















