Liigu sisu juurde

NULL PL/pgSQL-i IF NOT-is jättis 3683 ülesande kontrollita

Postgres

Vaata MarkdowninaAva rakenduses ClaudeAva rakenduses ChatGPTAva rakenduses Perplexity

Küsimus kopeeritakse ka. Kui vestlus avaneb tühjana, kleepige see sisse.

Lühidalt

SECURITY DEFINER funktsiooni sees oli õiguse kontroll kujul if not (has_perm or created_by = me or assignee_id = me) then raise. 2026. aasta juunis ei olnud rakenduse kõigil 3683 ülesandel loojat ega vastutajat. false OR NULL OR NULL on NULL, NOT NULL on endiselt NULL ja IF jätab oma keha vahele, kui tingimus on NULL. Seega ei tõstetud viga kunagi ja iga sisseloginud kasutaja sai ülesandeid kloonida. Parandus on coalesce(..., false) kogu tingimuse ümber.

See juhtus audiitorfirma ajaarvestuse rakenduses, funktsioonis, mis seisab “kloonimise” taga.

Milline see kontroll oli?

if not ( has_perm('tasks', 'create')
         or v_task.created_by = auth.uid()
         or v_task.assignee_id = auth.uid() ) then
  raise exception 'forbidden';
end if;

See loeb nagu “keela, kui sa ei tohi ülesandeid luua, ei teinud seda ise ega oma seda”. 3683 ülesannet oli automaatselt külvatud, seega olid created_by ja assignee_id igaühel NULL. Rolli puhul, millel kõik õigused olid välja lülitatud, oli tingimus NOT (false OR NULL OR NULL), mis on NULL, ja raise ei käivitunud kunagi.

Miks on sama tingimus RLS-is turvaline?

Row level security käsitleb kõike, mis ei ole true, keeldumisena, seega NULL tähendab ei. PL/pgSQL IF käsitleb kõike, mis ei ole true, kui “jäta keha vahele”, ja siin oli keha just keeldumine. PostgreSQL-i dokumentatsioon ütleb mõlemad pooled välja, row security ja IF kohta; koos muudavad need sama avaldise ühes kohas keeluks ja teises loaks.

Sama NULL, kaks vastupidist vastust. has_perm on false, created_by ja assignee_id on NULL. RLS-poliitikas: false OR NULL → NULL → Rida keelati; IF NOT (...) THEN RAISE korral: false OR NULL → NOT NULL on NULL → RAISE jäi vahele → Kloon lubatud.
Kolmeväärtuseline loogika, nagu seda kirjeldab PostgreSQL 18 dokumentatsioon, jaotised 5.9 (row security) ja 41.6.4 (IF).

SECURITY DEFINER funktsioon töötab ka RLS-ist mööda, seega on selle enda kontroll ainus, mis kehtib. Üks NULL haru piisab: ülejäänud harud võivad kõik olla false.

Mis selle parandas?

Mähkige kogu tingimus nii, et NULL muutub keeluks, ja käsitlege kloonimist ülesande loomisena, mitte lähteülesande muutmisena:

if not coalesce((select has_perm('tasks', 'create')), false) then
  raise exception 'forbidden' using errcode = '42501';
end if;

Kas see juhtus uuesti?

Jah, umbes kolm kuud hiljem, veel kolmes funktsioonis: ülesande arhiveerimine, selle tüübi muutmine ja kontroll, kas sellele tohib aega logida. SQL-i lugemine neid ei tabanud. Neid tabas rollipõhine RLS-i tõestus, sest see lülitab iga õiguse välja ja kontrollib, et toiming lükatakse tagasi.

Kuidas otsida?

grep 'if not (' leiab süntaksi, mitte vea. Mis töötas:

  1. Tõmmake kõigi funktsioonide kehad välja pg_get_functiondef-iga ühe päringuga.
  2. Igas if not (...) then ja if a <> b then eemaldage coalesce(...), exists(...) ja (select ...). Paljas or-ahel või võrdlus nullitava veeru üle on kandidaat.
  3. Sobitage ka if not <variable> then ja jälgige, kus muutuja väärtustatakse. _ok := has_perm(...) or created_by = me; peidab sama vea kahel real.
  4. Kutsuge iga abifunktsiooni, mida kasutatakse kujul if not helper(...), kolmel viisil tagasi keeratud tehingus: tundmatu id, NULL argument ja päris kasutaja, kelle oodatav rida puudub. Abifunktsioon, mis tagastab neist mis tahes juhul NULL, vajab coalesce-i kutsekohas või sees.

Selles rakenduses kontrollis läbivaatus 14 abifunktsiooni, kõik juba turvalised. Viga piirdus kloonimisfunktsiooni ja hiljem leitud kolmega. Seotud mitme rentnikuga lünk: mida RLS kahe org-i kasutaja puhul kahe silma vahele jätab. Rakendus oli audiitorettevõtte tööaja jälgija; selle muud vaiksed vead on artiklis tööaja arvestus audiitorettevõttele. Supabase turvakontroll loetleb SECURITY DEFINER funktsioonid oma 46 reegli hulgas, kuid ei oska öelda, kas nende sees olev IF jääb vea korral lahti, seega see läbivaatus jääb käsitööks.

© 2026 Denys Zaichenko. Algne artikkel: https://madsalt.tech/et/notes/plpgsql-if-not-null-fails-open/