Pāriet uz saturu

NULL PL/pgSQL IF NOT izlaida pārbaudi 3683 uzdevumiem

Postgres

Skatīt kā MarkdownAtvērt pakalpojumā ClaudeAtvērt pakalpojumā ChatGPTAtvērt pakalpojumā Perplexity

Jautājums tiek arī nokopēts. Ja tērzēšana atveras tukša, ielīmējiet to.

Īsumā

Atļaujas pārbaude SECURITY DEFINER funkcijā bija if not (has_perm or created_by = me or assignee_id = me) then raise. 2026. gada jūnijā visiem 3683 lietotnes uzdevumiem nebija ne izveidotāja, ne atbildīgā. false OR NULL OR NULL ir NULL, NOT NULL joprojām ir NULL, un IF izlaiž savu pamatdaļu, ja nosacījums ir NULL. Tāpēc kļūda netika izmesta nekad, un jebkurš pieteicies lietotājs varēja klonēt uzdevumus. Labojums ir coalesce(..., false) ap visu nosacījumu.

Tas notika revīzijas uzņēmuma darba laika uzskaites lietotnē, funkcijā, kas stāv aiz “klonēt uzdevumu”.

Kā izskatījās pārbaude?

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;

Tā skan kā “atsaki, ja vien tu nedrīksti veidot uzdevumus, neizveidoji šo vai nepieder tev”. 3683 uzdevumi bija iesēti automātiski, tāpēc created_by un assignee_id visiem bija NULL. Lomai ar izslēgtām visām atļaujām nosacījums bija NOT (false OR NULL OR NULL), kas ir NULL, un raise nekad neizpildījās.

Kāpēc tas pats nosacījums RLS ir drošs?

Row level security visu, kas nav true, uztver kā atteikumu, tāpēc NULL nozīmē nē. PL/pgSQL IF visu, kas nav true, uztver kā “izlaid pamatdaļu”, un šeit pamatdaļa bija tieši atteikums. PostgreSQL dokumentācija nosaka abas puses, par row security un par IF; kopā tās to pašu izteiksmi vienā vietā pārvērš aizliegumā, bet otrā atļaujā.

Tas pats NULL, divas pretējas atbildes. has_perm ir false, created_by un assignee_id ir NULL. RLS politikā: false OR NULL → NULL → Rinda atteikta; IF NOT (...) THEN RAISE gadījumā: false OR NULL → NOT NULL ir NULL → RAISE izlaists → Klons atļauts.
Trīsvērtību loģika, kā to apraksta PostgreSQL 18 dokumentācija, sadaļas 5.9 (row security) un 41.6.4 (IF).

SECURITY DEFINER funkcija darbojas arī apejot RLS, tāpēc spēkā ir tikai tās pašas pārbaude. Pietiek ar vienu NULL zaru: visi pārējie var būt false.

Kas to izlaboja?

Ietiniet visu nosacījumu tā, lai NULL kļūtu par atteikumu, un uztveriet klonēšanu kā uzdevuma izveidi, nevis avota rediģēšanu:

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

Vai tas atkārtojās?

Jā, apmēram pēc trim mēnešiem, vēl trijās funkcijās: uzdevuma arhivēšana, tā tipa maiņa un pārbaude, vai uz tā drīkst reģistrēt laiku. SQL lasīšana tās nenotvēra. Notvēra RLS pierādījums pa lomām, jo tas izslēdz katru atļauju un pārbauda, vai darbība tiek noraidīta.

Kā meklēt?

grep 'if not (' atrod sintaksi, nevis kļūdu. Kas strādāja:

  1. Ar vienu vaicājumu izvelciet visu funkciju pamatdaļas ar pg_get_functiondef.
  2. Katrā if not (...) then un if a <> b then noņemiet coalesce(...), exists(...) un (select ...). Kailā or ķēde vai salīdzinājums ar kolonnu, kas var būt NULL, ir kandidāts.
  3. Meklējiet arī if not <variable> then un izsekojiet, kur mainīgajam piešķir vērtību. _ok := has_perm(...) or created_by = me; paslēpj to pašu kļūdu divās rindās.
  4. Katru palīgfunkciju, ko lieto kā if not helper(...), izsauciet trīs veidos atceltā transakcijā: nezināms id, NULL arguments un īsts lietotājs, kuram gaidītās rindas nav. Palīgfunkcijai, kas jebkurā no tiem atgriež NULL, vajadzīgs coalesce izsaukuma vietā vai iekšpusē.

Tajā lietotnē pārbaude aptvēra 14 palīgfunkcijas, visas jau bija drošas. Kļūda aprobežojās ar klonēšanas funkciju un trim vēlāk atrastajām. Saistīta vairāku nomnieku nepilnība: ko RLS izlaiž lietotājam divās org.. Lietotne bija revīzijas uzņēmuma darba laika uzskaites rīks; tā citas klusās kļūdas ir rakstā darba laika uzskaite revīzijas uzņēmumam. Supabase drošības pārbaude savos 46 noteikumos uzskaita SECURITY DEFINER funkcijas, bet nevar pateikt, vai IF tajās izlaiž pārbaudi (fails open), tāpēc šī pārlūkošana paliek manuāla.

© 2026 Denys Zaichenko. Oriģinālais raksts: https://madsalt.tech/lv/notes/plpgsql-if-not-null-fails-open/