poważny błąd w postgresie

wykryto właśnie poważny błąd w postgresie w obsłudze funkcji typu “security definer". błąd tyczy się wszystkich wersji postgresa od kiedy to wprowadzono – czyli od 7.3.

błąd pozwala na wykonanie dowolnego kodu z podwyższonymi uprawnieniami i daje się wykorzystać podobno w przypadku większości funkcji zdefiniowany jako security definer (ale nie wszystkich).

na szybko kod można poprawić i zabezpieczyć ustawiając na sztywno w funkcji search_path'a.

prace nad poprawieniem tego trwają.

drop table if exists

mysql ma fajną rzecz – drop table if exists.

pozwala to na pisanie bezpiecznych skryptów sql'owych które nawet przy obudowaniu w transakcję się prawidłowo wykonają.

przykład?

załóżmy, że chcemy stworzyć tabelę i cośtam jeszcze zrobić – wszystko w transakcji.

BEGIN;
CREATE TABLE test (x serial PRIMARY KEY, test_value TEXT);
...
COMMIT;

wygląda niewinnie.

no tak, ale co jeśli ta tabela już istnieje – bo np. ktoś ma zainstalowaną wcześniejszą wersję bazy? trywiał zmieniamy na:

BEGIN;
DROP TABLE test;
CREATE TABLE test (x serial PRIMARY KEY, test_value TEXT);
...
COMMIT;

i już działa.

ale nie. jeśli jednak odpalimy tego sql'a na bazie w której naszej tabeli nie ma – drop table sie nie uda, więc cała transakcja zostanie zrollbackowania.

no cóż, to może tak?

DROP TABLE test;
BEGIN;
CREATE TABLE test (x serial PRIMARY KEY, test_value TEXT);
...
COMMIT;

teraz – drop jest poza transakcją, więc jak się wywali to nie problem.

hmm .. ale co jeśli tabela test jest, ale nasze zapytania po create table się wywalą? tabela test zostanie permanentnie skasowana, a polecenie zakładające zostanie zrollbackowane. tragedia.

czy nie da się tego sensownie zrobić? da się.

trzeba użyć funkcji execute() o której pisałem wczoraj.

dzięki niej, mogę zrobić swojego sql'a tak:

BEGIN;
SELECT execute('DROP TABLE test') WHERE exists (SELECT * FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'test');
CREATE TABLE test (x serial PRIMARY KEY, test_value TEXT);
COMMIT;

i uzyskuję w ten sposób funkcjonalny odpowiednik DROP TABLE … IF EXISTS;

oczywiście składnia jest trudniejsza, ale nie wydaje się to być dużym problemem – jeśli jest – no cóż. zawsze można napisać własną funkcję która zrobi ‘drop table' tylko jeśli tabela istnieje. ale wykorzystanie ogólnego mechanizmu ‘execute' wydaje się być zdecydowanie potężniejsze

zestawienie szybkiego serwisu

na temat tworzenia serwisów internetowych powstało sporo tekstów. na temat przyspieszania i ogólnie dbania o wydajność też. zasadniczo ciężko jest napisać coś nowego.

ale dzis przeczytałem bardzo fajny wpis na “mysql performance blog". nie zawiera on niczego rewolucyjnego. ale idealnie wręcz sie nadaje jako “check-lista" do wypuszczania wysoko wydajnych aplikacji. ot tak, aby o żadnym szczególiku nie zapomnieć.

nieoczekiwany sojusznik w walce z drm’em

czy napiszę o jobsie i jego otwartym liście do riaa? nie. w/g mnie to tani chwyt pod publiczkę.

o kolesiu z linspire który zażądał od jobsa czynów a nie gadania? też nie. to jest koleś który w ogóle się w świecie nie liczy i wykorzystuje sytuację aby zdobyć chwilę “widoczności antenowej".

o czym więc?

otóż – dosyć nieoczekiwanym sojusznikiem okazała się być firma emi. kojarzycie? 3 na świecie wydawca muzyki (biorąc pod uwagę wartość sprzedaży). w ich “portfolio" wchodzą takie grupy/wykonawcy jak kate bush, pet shop boys, paul mccartney, tomoyashu hotei, massive attack, iggy pop, nick cave and the bad seeds, coldplay, queen, robbie williams, radiohead, depeche mode, liz phair, tina turner, the beach boys, enigma, the rolling stones, kraftwerk, david bowie, ub40, pink floyd, moby, gorillaz, sarah brightman, lenny kravitz, iron maiden, blur, the chemical brothers, koЯn, beastie boys, the beatles czy placebo. a to nawet nie jest połowa artystów tworzących w barwach emi.

co takiego zrobiło emi? otóż emi od jakiegoś już czasu sprzedaje muzykę swojej trzódki (małej jej części na razie) całkowicie bez żadnych ograniczeń, drm'ów czy innego badziewstwa. i jak mówią – oni są zadowoleni i klienci sa zadowoleni.

podobno już teraz planują sprzedaż całości swojej dyskografii w takiej właśnie bez-drm'owej postaci.

jak widać – wyłomy się pojawiają nawet w, zdawałoby się monolitycznej,  strukturze riaa.

ś.p. spinacz

pamiętacie jak w 1997 roku microsoft dołączył do office'a wkurzające coś, co wyglądało jak spinacz do papieru, gadało i ogólnie było maksymalnie upierdliwe?

no cóż. w office 2003 clippy (tak się to coś nazywało) był domyślnie wyłączony – ale dawało się go włączyć.

natomiast w office 2007 – clippy'ego już po prostu nie ma. chlip. żegnaj. byłeś bohaterem wielu żartów, komiksów i złośliwości. ale teraz odszedłeś do wielkiego /dev/null'a.

masowe nadawanie praw

co jakiś czas pojawia mi się potrzeba masowego nadania praw. np. – mam nowego człowieka i muszę mu dać prawa do odczytu wszystkich tabelek w bazie. niestety postgresql nie obsługuje składni typu ‘GRANT … ON *'.

cóż więc pozostaje. pl/pgsql 🙂

zacznijmy od podstaw. funkcja która daje uprawnienia do czytania wskazanych tabel danemu użytkownikowi:

CREATE OR REPLACE FUNCTION grant_select_to(in_username TEXT, in_table_regexp TEXT) RETURNS void as $BODY$
DECLARE
temprec RECORD;
use_sql TEXT;
BEGIN
FOR temprec IN SELECT c.relname FROM pg_class c join pg_namespace n on c.relnamespace = n.oid WHERE c.relkind = 'r' AND c.relname ~* in_table_regexp AND n.nspname = 'public' LOOP
use_sql := 'GRANT SELECT ON TABLE ' || temprec.relname || ' TO ' || in_username;
raise notice 'sql to run: [%]', use_sql;
EXECUTE use_sql;
END LOOP;
RETURN;
END;
$BODY$ language plpgsql;

i już. teraz można:

select grant_select_to('depesz', '.');

co nada użytkownikowi depesz prawa do wszystkich (pasujących do regexpa ‘.', czyli wszystkich) tabel (relname = ‘r') w schemie public (inne schemy to zazwyczaj rzeczy systemowe).

do tego funkcja wypisze wszystkie wykonane sql'e.

no tak, ale co zrobić gdy chcemy dac inne prawa? albo kilku różnym użytkownikom różne zestawy praw? no i jeszcze dochodzą schemy – jak z nich korzystamy, to nie wystarczy dać prawa do tabel w schemach – trzeba dać też prawo ‘USAGE' do schemy.

można oczywiście napisać bardzo fajną funkcję która to wszystko zrobi. ale może zamiast tego, podejdziemy do sprawy sprytniej.

najpierw – to dzięki czemu mogę w plpgsql'u robić takie sztuczki to fakt, że jest tam funkcja execute. której w zwykłym sql'u nie ma. dodajmy ją więc:

CREATE OR REPLACE FUNCTION execute(in_sql TEXT) RETURNS void as $BODY$
DECLARE
BEGIN
EXECUTE in_sql;
RETURN;
END;
$BODY$ language plpgsql;

co teraz?

zacznijmy od takiego sql'a:

select * from information_schema.tables where table_schema = 'public';

to nam wyselectuje jakieś tam dane nt. tabelek w schemie public.

a jak zmienię tego sql'a na:

select 'grant select on table ' || table_name || ' to depesz' from information_schema.tables where table_schema = 'public';

wygląda interesująco.

więc kolejna drobna modyfikacja:

select execute('grant select on table ' || table_name || ' to depesz') from information_schema.tables where table_schema = 'public';

efekt – działa. w dodatku – ponieważ zapytanie które wykonuję mogę dowolnie zmieniać, to nadawanie różnych praw staje sie dziecinnie proste. filtrowanie po nazwie tabeli – trywiał, wystarczy dodać “and table_name ~* ‘…'".

dodatkowo – funkcja execute() ma też inne zastosowania. ale o nich następnym razem.