ข้าม​ไป​ยัง​เนื้อหา

ตาราง​ใบ​เดิม ทาง​เข้า​ห้า​ทาง และ​สอง​ทางใน​นั้น​ให้​หก​แถว

ผู้​อ่าน​ใน​บท​นี้​ไม่ใช่​เจ้าของ​ตาราง มัน​คือ role ชื่อ kaen_rls_app ซึ่ง​ตั้ง app.tenant เป็น clinic-a แล้ว​นับ​แถว​ของ​ตาราง t_view ตรงๆ ค่าที่​วัด​ได้​คือ noForceBase = 3

คำ​สั่ง​ถัด​ไป​ใน session เดียวกัน นับ​ผ่าน view ที่​เจ้าของ​ตาราง​สร้าง​ไว้ ซึ่ง​ชี้​ไป​ที่​ตาราง​ใบ​เดิม​ทุก​แถว ค่าที่​วัด​ได้​คือ noForceViewDefault = 6

สอง​คำ​สั่ง​นั้น​อยู่​ติด​กัน ตาราง​ใบ​เดียว policy ใบ​เดียว และ​ค่า​ตั้ง​ของ session ตัว​เดียว บท​นี้​นับ​ทาง​เข้า​ทั้งหมด​ที่ probe วัด แล้ว​ถาม​ว่า​อะไร​ปิด​ทาง​ที่​ให้​หก​ได้​บ้าง

📦 clinic — ซี​รีส์​เดิม และ​บท​นี้​ยัง​เป็น SQL ล้วน

คอร์ส​นี้​อยู่​ใน​ซี​รีส์​คลินิก​เดียว​กับ คอร์ส​คัด​กรอง​ผู้​เรียก และ คอร์ส CORS กับ CSRF แต่​บท​นี้​ไม่มี​แอป​มา​เกี่ยวข้อง มี​แต่​ตาราง view ฟังก์ชัน policy และ role

ตัวเลข​ทุก​ตัว​ใน​บท​นี้​คุณ​รัน​เอง​ได้ สั่ง scripts/clinic-rls/up.sh ซึ่ง​ยก container ชื่อ kaen-rls-pg ขึ้น​ที่ port 55438 แล้ว​สั่ง python3 scripts/clinic-rls/spike.py เก็บ​กวาด​ด้วย docker rm -f kaen-rls-pg

ทุก​ทาง​เข้า​ใน​บท​นี้​ชี้​ไป​ที่​ตาราง​ใบ​เดียวกัน และ​เจ้าของ​ตาราง​เป็น​คน​สร้าง​ทุก​ใบ

ทรง​ตั้ง​ต้น · สั่ง​โดย role ที่​เป็น​เจ้าของ​ตาราง
create table t_view (
id bigserial primary key,
tenant_id text not null,
patient text not null,
archived boolean not null default false);
insert into t_view (tenant_id, patient, archived) values
('clinic-a','Ann',false),('clinic-a','Ben',false),('clinic-a','Cid',true),
('clinic-b','Dan',false),('clinic-b','Eve',false),('clinic-b','Fay',true);
grant select, insert, update, delete on t_view to kaen_rls_app, kaen_rls_bypass;
grant usage, select on sequence t_view_id_seq to kaen_rls_app, kaen_rls_bypass;
alter table t_view enable row level security;
create policy p on t_view for all using (tenant_id = current_setting('app.tenant', true));
create view v_def as select * from t_view;
create view v_inv with (security_invoker = true) as select * from t_view;
grant select on v_def, v_inv to kaen_rls_app;
create function public.f_def() returns bigint language sql security definer
as $$ select count(*) from t_view $$;
create function public.f_inv() returns bigint language sql security invoker
as $$ select count(*) from t_view $$;

เมล็ด​เป็น​รูป​เดียว​กับ​บท​ที่ 1 คือ​หก​แถว สาม​แถว​ต่อ​ผู้​เช่า และ policy ใบ​เดียว​ที่​เทียบ tenant_id กับ​ค่า​ตั้ง​ของ session ตรง​ไป​ตรง​มา

ก้อน​นี้​สั่ง enable row level security อย่าง​เดียว ยัง​ไม่มี​บรรทัด​ไหน​สั่ง force บท​ที่ 1 วัด​ไว้​แล้ว​ว่า​เจ้าของ​ตาราง​ไม่​ได้​อยู่​ใต้ policy ของ​ตัว​เอง​ด้วย​คำ​สั่ง enable เพียง​คำ​สั่ง​เดียว

ห้า​คำ​สั่ง​ล่าง​คือ​ของ​ที่​บท​นี้​เพิ่ม​เข้า​มา view สอง​ใบ​ต่าง​กัน​ที่​คำ​ว่า security_invokersecurity_invoker (view option)ค่า​ตั้ง​ของ view ที่​ให้​ใช้​สิทธิ์​ของ​ผู้​เรียก​แทน​ของ​เจ้าของ view · ใต้​ทรง​ที่​ยัง​ไม่ FORCE view ธรรมดา​ของ​เจ้าของ​บน​ตาราง t_view ให้ 6 แถว (noForceViewDefault) ส่วน view ที่​ตั้ง​ค่า​นี้​เป็น true ให้ 3 แถว (noForceViewInvoker) · ค่า​ตั้ง​นี้​มี​ใน PostgreSQL ตั้งแต่ major 15 ใบ​เดียว และ​ฟังก์ชัน​สอง​ใบ​ต่าง​กัน​ที่​คำ​ว่า SECURITY DEFINERSECURITY DEFINERฟังก์ชัน​ที่​ทำงาน​ด้วย​สิทธิ์​ของ​ผู้​สร้าง​มัน ไม่ใช่​ของ​ผู้​เรียก · ใต้ ENABLE ที่​ยัง​ไม่ FORCE ผู้​เรียก​อ่าน​ตาราง t_view ตรงๆ ได้ 3 แถว (noForceBase) แต่​เรียก​ผ่าน​ฟังก์ชัน​แบบ​นี้​ของ​เจ้าของ​ตาราง​ได้ 6 แถว (noForceSecDefiner) · สั่ง FORCE แล้ว​ทาง​นี้​ได้ 3 แถว (forceSecDefiner) เท่ากับ​ทาง​ตรง กับ security invoker ใบ​เดียว​เช่น​กัน ตัว SQL ข้าง​ใน​เหมือน​กัน​ทุก​ตัว​อักษร

ห้า​คำ​สั่ง​ใน session เดียว ชี้​ไป​ที่​ตาราง​ใบ​เดียวกัน แล้ว​ได้ 3 สาม​ครั้ง และ 6 สอง​ครั้ง

ทาง​เข้า​ห้า​ทาง · session เดียว · ผู้​อ่าน​คือ kaen_rls_app
set app.tenant = 'clinic-a';
select 'KAEN|noForceBase|'||count(*) from t_view where true;
select 'KAEN|noForceViewDefault|'||count(*) from v_def where true;
select 'KAEN|noForceViewInvoker|'||count(*) from v_inv where true;
select 'KAEN|noForceSecDefiner|'||public.f_def();
select 'KAEN|noForceSecInvoker|'||public.f_inv();
ทาง​เข้าคีย์แถว​ที่​นับ​ได้
อ่าน​ตาราง t_view ตรงๆnoForceBase3
ผ่าน view v_def ที่​ไม่​ได้​ตั้ง​ค่า​อะไร​เพิ่มnoForceViewDefault6
ผ่าน view v_inv ที่​ตั้ง security_invoker = truenoForceViewInvoker3
เรียก public.f_def() ซึ่ง​เป็น security definernoForceSecDefiner6
เรียก public.f_inv() ซึ่ง​เป็น security invokernoForceSecInvoker3

สอง​แถว​ที่​ให้ 6 คือ​แถว​ที่​นับ​ได้​ทั้ง​ตาราง ผู้​อ่าน​คน​เดิม​ใน session เดียวกัน​เห็น​แถว​ของ clinic-b ครบ​ทุก​แถว ทั้ง​ที่​ค่า​ตั้ง​ของ​เขา​ยัง​เป็น clinic-a อยู่

เอกสาร​ของ PostgreSQL 18 อธิบาย​ว่า view ที่​ไม่​ได้​ตั้ง​ค่า​เพิ่ม อ่าน​ตาราง​ที่​อยู่​ข้าง​ใต้ ด้วย​สิทธิ์​ของ​ผู้​เป็น​เจ้าของ view และ​ฟังก์ชัน security definer ทำงาน​ด้วย​สิทธิ์​ของ ผู้​เป็น​เจ้าของ​ฟังก์ชัน ทั้ง​สอง​ใบ​ใน​ทรง​นี้​เป็น​ของ​เจ้าของ​ตาราง

ข้อ​นั้น​เป็น​ข้อ​อ้าง​เรื่อง​กลไก จึง​อ้าง​เอกสาร ส่วน​ตัวเลข 3 กับ 6 เป็น​สิ่ง​ที่ probe วัด บท​นี้​ไม่​ได้​พิสูจน์​ว่า​ตัว​ตัดสิน​อ่าน​สิทธิ์​ของ​ใคร​ก่อน​ใคร

สอง​ใบ​ที่​ให้ 3 คือ​ใบ​ที่​ประกาศ​ว่า​ใช้​สิทธิ์​ของ​ผู้​เรียก noForceViewInvoker และ noForceSecInvoker เท่ากับ noForceBase พอดี ทั้ง​ที่​คำ​สั่ง​เดิน​ผ่าน view และ​ผ่าน​ฟังก์ชัน​คนละ​ทาง

ค่า​ตั้ง​ที่​แยก v_inv ออก​จาก v_def ถูก​อ่าน​ออก​มา​จาก catalog จริง ไม่ใช่​เดา​จาก​คำ​สั่ง​ที่​สั่ง​ไป

อ่าน​ค่า​ตั้ง​ของ v_inv จาก pg_class
select 'KAEN|viewInvokerFlag|'||
coalesce(reloptions::text like '%security_invoker=true%', false)
from pg_class where oid = 'v_inv'::regclass;

viewInvokerFlag = true

คีย์​นี้​มี​อยู่​เพราะ view สอง​ใบ​ต่าง​กัน​ด้วย​คำ​ใน​บรรทัด create view เพียง​คำ​เดียว และ​คำ​ใน​บรรทัด​ที่​สั่ง​ไป ไม่ใช่​หลัก​ฐาน​ว่า​ฐาน​ข้อมูล​เก็บ​ค่า​นั้น​ไว้​จริง

ธง​ใบ​นี้​ถูก​วัด​กับ v_inv ใบ​เดียว probe ไม่​ได้​อ่าน​ค่า​ตั้ง​ของ v_def ออก​มา และ​ไม่​ได้​อ่าน​ของ​ฟังก์ชัน​สอง​ใบ​ด้วย ทาง​ที่​แยก​ฟังก์ชัน​สอง​ใบ​ออก​จาก​กัน​ใน​บท​นี้ คือ​จำนวน​แถว​ที่​มัน​คืน​กลับ​มา

ค่า​ตั้ง​นี้​ต้องการ major ที่​ไม่​ต่ำ​กว่า 15

security_invoker ของ view มาใน PostgreSQL 15 ตาม release notes ของ​รุ่น​นั้น ทรง​ที่​วัด noForceViewInvoker จึง​วัด​ไม่​ได้​เลย​บน major ที่​เก่า​กว่า​นั้น และ container ของ​คอร์ส​นี้​ตรึง​ไว้​ที่ 18.6

คำ​สั่ง​เดียว​ที่​เจ้าของ​ตาราง​สั่ง แล้ว​สอง​ทาง​ที่​ให้​หก ลง​มา​เท่ากับ​อีก​สาม​ทาง

สั่ง​โดย​เจ้าของ​ตาราง · ไม่​แตะ SQL ของ policy สัก​ตัว​อักษร
alter table t_view force row level security;
ทาง​เข้า​ห้า​ทาง​เดิม · คำ​สั่ง​ชุด​เดิม · ผู้​อ่าน​คน​เดิม
set app.tenant = 'clinic-a';
select 'KAEN|forceBase|'||count(*) from t_view where true;
select 'KAEN|forceViewDefault|'||count(*) from v_def where true;
select 'KAEN|forceViewInvoker|'||count(*) from v_inv where true;
select 'KAEN|forceSecDefiner|'||public.f_def();
select 'KAEN|forceSecInvoker|'||public.f_inv();
ทาง​เข้าคีย์แถว​ที่​นับ​ได้
อ่าน​ตาราง t_view ตรงๆforceBase3
ผ่าน view v_defforceViewDefault3
ผ่าน view v_invforceViewInvoker3
เรียก public.f_def()forceSecDefiner3
เรียก public.f_inv()forceSecInvoker3

FORCE ROW LEVEL SECURITYALTER TABLE … FORCE ROW LEVEL SECURITYคำ​สั่ง​ที่​ยก​ธง​ใบ​ที่​สอง ซึ่ง​พา​เจ้าของ​ตาราง​เข้า​มา​อยู่​ใต้ policy ด้วย · ทุก​ค่า​ต่อ​จาก​นี้​วัด​บน​ตาราง t_owner ที่​เจ้าของ​อ่าน​เอง · เติม​แล้ว​เจ้าของ​ที่​ไม่​ได้​ตั้ง GUC ได้ 0 แถว ตั้ง​เป็น​ผู้​เช่า a ได้ 3 แถว ตั้ง​เป็น b ได้ 3 แถว และ​สั่ง NO FORCE ทั้ง​ที่ GUC ยัง​ตั้ง​อยู่ กลับ​ไป​ได้ 6 แถว โดย SQL ของ policy ไม่​เปลี่ยน​สัก​ตัว​อักษร​ใน​ทุก​ทรง คือ​คำ​สั่ง​ที่​พา​เจ้าของ​ตาราง​เข้า​มา​อยู่​ใต้ policy ด้วย สอง​ทาง​ที่​เดิน​ด้วย​สิทธิ์​ของ​เจ้าของ จึง​เดิน​เข้า​มา​อยู่​ใต้ policy ตาม​ไป​ด้วย

ค่าที่​ขยับ​คือ​สอง​ตัว​ที่​เคย​เป็น​หก forceViewDefault และ forceSecDefiner เป็น 3 ส่วน​อีก​สาม​ตัว​เท่า​เดิม​ทุก​ตัว การ​สั่ง force จึง​ไม่​ได้​เปลี่ยน​ทาง​ที่​ปิด​อยู่​แล้ว

session ถัด​ไป​ไม่มี​ใคร​สั่ง set app.tenant แล้ว​วัด​สอง​ทาง​ที่​เพิ่ง​ถูก​ปิด​ซ้ำ

session ใหม่ · ไม่มี​คำ​สั่ง set app.tenant อยู่​ข้าง​หน้า
select 'KAEN|forceNoGucViewDefault|'||count(*) from v_def where true;
select 'KAEN|forceNoGucSecDefiner|'||public.f_def();

forceNoGucViewDefault = 0 และ forceNoGucSecDefiner = 0

policy ใบ​เดิม​อ่าน​ค่า​ตั้ง​ที่​ยัง​ไม่มี​ใคร​ตั้ง แล้ว​ไม่มี​แถว​ไหน​ผ่าน ทั้ง​ทาง​ของ view และ​ทาง​ของ​ฟังก์ชัน สอง​ทาง​นี้​จึง​พา​ค่า​ตั้ง​ของ session ที่​เรียก​มัน​ติด​ไป​ด้วย

สอง​คีย์​นี้​วัด​แค่​สอง​ทาง probe ไม่​ได้​วัด​ทาง​ตรง​และ​ไม่​ได้​วัด​สอง​ทาง​ที่​ประกาศ​ว่า​ใช้ สิทธิ์​ของ​ผู้​เรียก​ใน​ทรง​นี้ ตัวเลข 0 จึง​เป็น​ของ v_def กับ public.f_def() เท่านั้น

force ยัง​เปิด​อยู่ ตาราง​ใบ​เดิม view ใบ​เดิม ฟังก์ชัน​ใบ​เดิม เปลี่ยน​ที่​นิพจน์​ของ policy อ่าน

เจ้าของ​ตาราง​เปลี่ยน policy · FORCE ยัง​เปิด​อยู่​จาก​หัวข้อ​ก่อน​หน้า
drop policy p on t_view;
create policy p on t_view for all
using (tenant_id = 'clinic-a' and current_user = 'kaen_rls_app');
ทาง​เข้า​ห้า​ทาง​เดิม · session ใหม่ · ไม่มี​คำ​สั่ง set app.tenant อยู่​ข้าง​หน้า
select 'KAEN|currentUserBase|'||count(*) from t_view where true;
select 'KAEN|currentUserViewDefault|'||count(*) from v_def where true;
select 'KAEN|currentUserViewInvoker|'||count(*) from v_inv where true;
select 'KAEN|currentUserSecDefiner|'||public.f_def();
select 'KAEN|currentUserSecInvoker|'||public.f_inv();
ทาง​เข้าคีย์แถว​ที่​นับ​ได้
อ่าน​ตาราง t_view ตรงๆcurrentUserBase3
ผ่าน view v_defcurrentUserViewDefault3
ผ่าน view v_invcurrentUserViewInvoker3
เรียก public.f_def()currentUserSecDefiner0
เรียก public.f_inv()currentUserSecInvoker3

force ยัง​เปิด​อยู่​ทั้ง​ทรง ไม่มี​ใคร​สั่ง no force และ​ไม่มี​ใคร​แตะ view หรือ​ฟังก์ชัน​สัก​ใบ สิ่ง​ที่​เปลี่ยน​คือ​นิพจน์​ของ policy อ่าน​ของ​คนละ​อย่าง และ session ของ​ห้า​คำ​สั่ง​นี้ ไม่​ได้​สั่ง set app.tenant เลย เพราะ​นิพจน์​ใบ​ใหม่​ไม่​ได้​อ่าน​ค่า​ตั้ง​นั้น

ทาง​ของ SECURITY DEFINER ให้ 0 ทาง​เดียว ใน​ทรง​ก่อน​หน้า ทาง​เดียวกัน​นี้​ให้ forceSecDefiner = 3 นิพจน์​ใหม่​เทียบ current_user กับ kaen_rls_app และ​เอกสาร​ระบุ​ว่า​ฟังก์ชัน​แบบ​นี้​ทำงาน​ด้วย​สิทธิ์​ของ​ผู้​เป็น​เจ้าของ​มัน

ค่าที่​วัด​ได้​สอดคล้อง​กับ​ข้อ​ที่​เอกสาร​กำหนด บท​นี้​ไม่​ได้​พิสูจน์​ว่า current_user ถูก​ประเมิน​เป็น​ชื่อ​ใด​ข้าง​ใน​แต่ละ​ทาง มัน​วัด​จำนวน​แถว​ที่​ผู้​เรียก​ได้​กลับ​ไป​เท่านั้น

เอา​สามตา​ราง​ข้าง​บน​มา​วาง​ซ้อน​กัน แล้ว​จะ​เห็น​ว่า​คีย์​ที่​ขยับ​มี​อยู่​สอง​คีย์ และ​มัน​ขยับ​คนละ​ครั้ง

flowchart LR
  S["kaen_rls_app · ตั้ง app.tenant เป็น clinic-a"] --> D1["อ่าน t_view ตรงๆ"]
  S --> D2["ผ่าน v_def · view ที่ไม่ได้ตั้งค่าเพิ่ม"]
  S --> D3["ผ่าน v_inv · security_invoker = true"]
  S --> D4["เรียก public.f_def · security definer"]
  S --> D5["เรียก public.f_inv · security invoker"]
  D1 --> R1["noForceBase 3 · forceBase 3 · currentUserBase 3"]
  D2 --> R2["noForceViewDefault 6 · forceViewDefault 3 · currentUserViewDefault 3"]
  D3 --> R3["noForceViewInvoker 3 · forceViewInvoker 3 · currentUserViewInvoker 3"]
  D4 --> R4["noForceSecDefiner 6 · forceSecDefiner 3 · currentUserSecDefiner 0"]
  D5 --> R5["noForceSecInvoker 3 · forceSecInvoker 3 · currentUserSecInvoker 3"]

คำ​บรรยาย​ภาพ: ห้า​ทาง​เข้า​ที่ probe วัด และ​ค่า​ของ​แต่ละ​ทางใน​สาม​สถานะ​เรียง​จาก​ซ้าย​ไป​ขวา คือ​ยัง​ไม่​สั่ง force สั่ง force แล้ว และ​เปลี่ยน policy ไป​อ่าน current_user สาม​แถว​ที่​ไม่​ขยับ​เลย​สัก​สถานะ​คือ R1 ทาง​ตรง R3 v_inv และ R5 f_inv ส่วน​สอง​แถว​ที่​ขยับ​คือ R2 v_def และ R4 f_def และ​มัน​ขยับ​คนละ​ครั้ง​กัน

v_def ขยับ​ตอน​สั่ง force แล้ว​นิ่ง​หลัง​จาก​นั้น ส่วน public.f_def() ขยับ​ตอน​สั่ง force เหมือน​กัน แล้ว​ขยับ​อีก​ครั้ง​ตอน policy เปลี่ยน​ไป​อ่าน​ของ​คนละ​อย่าง

ตัว​ที่​ปิด​ทาง​ได้ ขึ้น​กับ​ว่า policy อ่าน​อะไร ไม่​ได้​ขึ้น​กับ​ชื่อ​ของ​กลไก​ที่​ปิด

security_invoker ปิด​ทาง​ของ view ได้​ตั้งแต่​ก่อน​สั่ง force คือ noForceViewInvoker = 3 ขณะ​ที่ noForceViewDefault = 6 แต่​มัน​ปิด​ได้​ทาง​เดียว ทาง​ของ​ฟังก์ชัน​ใน​สถานะ​เดียวกัน​ยัง​เป็น noForceSecDefiner = 6

force ปิด​ได้​ทั้ง​สอง​ทาง​พร้อม​กัน ใน​สถานะ​ที่ policy อ่าน​ค่า​ตั้ง​ของ session ห้า​คีย์​ใน​สถานะ​นั้น​เป็น 3 ทั้งหมด เพราะ​ค่าที่ policy อ่าน เดิน​ไป​กับ session ของ​ผู้​เรียก ไม่​ได้​เดิน​ไป​กับ​สิทธิ์​ที่​คำ​สั่ง​ใช้

แต่ force ที่​ยัง​เปิด​อยู่​ตัว​เดิม ไม่​ได้​ทำให้​ห้า​ทาง​เท่า​กัน​ใน​สถานะ​ที่​สาม currentUserSecDefiner = 0 ขณะ​ที่​อีก​สี่​คีย์​เป็น 3 สิ่ง​ที่ policy อ่าน​ใน​สถานะ​นั้น เป็น​สิ่ง​ที่ทาง​ของ​ฟังก์ชัน​เปลี่ยน​ไป​ด้วย

ทั้ง​สาม​อย่าง​จึง​ไม่​ได้​เรียง​กัน​เป็น​บันได คำถาม​ที่​ตอบ​ได้​คือ นิพจน์​ของ policy อ่าน​ของ​ที่​เดิน​ไป​กับ session หรือ​อ่าน​ของ​ที่​เดิน​ไป​กับ​สิทธิ์ และ​คำ​ตอบ​ของ​ข้อ​นั้น ตัดสิน​ว่า​ทาง​ไหน​ยัง​ไม่​ถูก​ปิด

การ​นับ policy ที่​ผูก​อยู่ ตอบ​คำถาม​นี้​ไม่​ได้

ทั้ง​สาม​สถานะ​ใน​บท​นี้​มี policy ผูก​อยู่​กับ t_view ตลอด และ​ตัวเลข​ใน​ตาราง​เดิน​จาก 6 ลงไป 3 และ​ลง​ไป 0 โดย​จำนวน​ใบ​ไม่​ได้​เปลี่ยน

ค่าที่​ต่าง​กัน​ใน​สาม​สถานะ​มา​จาก​สอง​อย่าง​ที่​อยู่​คนละ​ที่​กับ​ตัว​นับ คือ​ธง​บน​ตาราง ซึ่ง​บท​ที่ 1 วัด​ออก​มา​จาก catalog และ​สิ่ง​ที่​นิพจน์​ของ policy หยิบ​มา​เทียบ

🔁 บท​ที่​ต่อ​กับ​บท​นี้​โดยตรง

สิบ​แปด​คีย์​ใน​บท​นี้​มา​จาก SQL ที่​รัน​จริง ส่วน​คำ​อธิบาย​ว่า​ทาง​ไหน​เดิน​ด้วย​สิทธิ์​ของ​ใคร มา​จาก​เอกสาร

spike.py รัน​ชุด​ข้อ​อ้าง​ทั้ง​ชุด​สาม​รอบ​ต่อ​การ​เรียก​หนึ่ง​ครั้ง แล้ว​เทียบ​ผล​ที​ละ​คีย์ รอบ​ที่​รัน​เมื่อ 2026-09-06 ได้ 52 ข้อ​อ้าง​ตรง​ทั้งหมด 98 ค่าที่​วัด​ได้ และ 0 ค่าที่​ขยับ​ระหว่าง​รอบ

สิ่ง​ที่​บท​นี้ ไม่​ได้ พิสูจน์​คือ​กลไก​ข้าง​ใน บท​นี้​ไม่​ได้​แสดง​ว่า​ตัว​ตัดสิน​ตั้ง​สิทธิ์​ของ คำ​สั่ง​ไว้​เป็น​ชื่อ​ใด​ตอน​เดิน​ผ่าน view และ​ตอน​เดิน​ผ่าน​ฟังก์ชัน ข้อ​อ้าง​แบบ​นั้น อ้าง​เอกสาร​ของ PostgreSQL 18 และ​เอกสาร​เป็น​คนละ​หลัก​ฐาน​กับ probe

ตัวเลข​ทุก​ตัว​ผูก​กับ​รุ่น​ที่​ระบุ​ไว้

ผล​ทั้งหมด​ใน​บท​นี้​วัด​บน PostgreSQL 18.6 บน aarch64-unknown-linux-musl ใน container ของ​คอร์ส​นี้​เอง major อื่น​เป็น​คนละ​การ​วัด และ​บท​นี้​ไม่​ได้​อ้าง​ถึง​มัน

หน่วย​ของ​หลัก​ฐาน​ใน​บท​นี้​มี​สอง​หน่วย คือ​จำนวน​แถว​ที่​ผู้​เรียก​นับ​ได้ และ​ธง​บู​ลี​นที่​อ่าน​ออก​มา​จาก pg_class ทั้ง​คอร์ส​ไม่มี​หน่วย​เวลา​สัก​ตัว

รู้​แล้ว​ว่า​ทาง​เข้า​มี​หลาย​ทาง ยัง​ไม่รู้​ว่า​หน่วย​ที่​ใช้​ดู​มัน​อยู่ มอง​อะไร​ไม่​เห็น

ทุก​ตัวเลข​ใน​บท​นี้​เป็น​จำนวน​แถว​ที่​ผู้​เรียก​นับ​ได้ และ​ทุก​ครั้ง​ที่ทาง​เข้า​ทาง​หนึ่ง​ถูก​ปิด ตัวเลข​ของ​ทาง​นั้น​ก็​ขยับ​ให้​เห็น หน่วย​นี้​จึง​ทำงาน​ได้​ตลอด​ห้า​ทาง

แต่​ทั้ง​สาม​สถานะ​ข้าง​บน​เปลี่ยนจำนวนแถว​เสมอ ยัง​ไม่มี​ทรง​ไหน​ใน​คอร์ส​ที่​ผู้​เช่า​ซึ่ง​เป็น เจ้าของ​แถว​เปลี่ยน​ไป โดยที่​จำนวน​แถว​ค้าง​อยู่​ที่​เดิม

บท​ที่ 6 วัด​ทรง​นั้น policy เรียก​ฟังก์ชัน​ที่​ไม่​ได้​ตรึง​ค่า​ตั้ง​ของ​การ​ค้น​ชื่อ แล้ว​ผู้​โจมตี​วาง​ของ​ตัว​เอง​ไว้​ข้าง​หน้า ยอด​รวม​ไม่​ขยับ​สัก​แถว และ​หน่วย​ที่​ใช้​ทั้ง​บท​นี้ มอง​ไม่​เห็น​อะไร​เลย


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • PostgreSQL 18 — Row Security Policies (เข้า​ถึง 2026-09-06) — ที่มา​ของ​กฎ​ว่า​เจ้าของ​ตาราง​ถูก​ยกเว้น​จาก policy ตาม​ค่า​ปริยาย และ FORCE คือ​สิ่ง​ที่​ถอน​การ​ยกเว้น​นั้น

  • PostgreSQL 18 — CREATE VIEW (เข้า​ถึง 2026-09-06) — ความ​หมาย​ของ​ค่า​ตั้ง security_invoker · หน้า​นี้​เอ่ย​ชื่อ​ค่า​ตั้ง​เจ็ด​ครั้ง แต่​ไม่​ระบุ major ที่​มัน​เริ่ม​มี​สัก​ครั้ง

  • PostgreSQL 15 — release notes §E.20.3.1.6 Privileges (เข้า​ถึง 2026-09-06) — major ที่ security_invoker เริ่ม​มี · “Allow table accesses done by a view to optionally be controlled by privileges of the view’s caller”

  • PostgreSQL 18 — CREATE FUNCTION (เข้า​ถึง 2026-09-06) — ความ​หมาย​ของ SECURITY DEFINER และ SECURITY INVOKER

  • PostgreSQL 18 — System Information Functions (เข้า​ถึง 2026-09-06) — ความ​หมาย​ของ current_user ซึ่ง​นิพจน์​ของ policy ใน​สถานะ​ที่​สาม​หยิบ​มา​เทียบ

  • ตัวเลข​ทุก​ตัว​ใน​บท​นี้​มา​จาก scripts/clinic-rls/spike.py ซึ่ง​สร้าง​ฐาน​ข้อมูล​กับ role ขึ้น​ใหม่​ทุกร​อบ แล้ว​รื้อ​ทิ้ง​เมื่อ​จบ · ต้อง​สั่ง scripts/clinic-rls/up.sh ก่อน ซึ่ง​ยก container ของ​คอร์ส​นี้​เอง​ขึ้น​ที่ port 55438

เช็กความเข้าใจ — บทที่ 5

ข้อ 1 / 3

ตาราง `t_view` สั่ง `enable row level security` อย่างเดียว ยังไม่สั่ง `force` แล้ว app role ตั้ง `app.tenant` เป็น `clinic-a` นับผ่าน view ที่เจ้าของตารางสร้างไว้แบบไม่ตั้งค่าเพิ่ม ได้กี่แถว