Přeskočit na obsah
Magazín Chatujme.cz
Programování · čtení na 5 min

Linux neumí vytvořit a otevřít adresář jedním voláním. Návrh na změnu dělí vývojáře jádra

Kdo v Linuxu zakládá adresář a chce mu hned nastavit práva, musí to udělat na dva kroky. V mezeře mezi nimi mu ho může někdo vyměnit. Série záplat Joriho Koolstry chce tu mezeru zavřít tím, že vzkřísí kombinaci příznaků O_CREAT a O_DIRECTORY, kterou jádro tři roky odmítá s chybou. Správce vrstvy VFS je pro, dva vývojáři varují, že program nemá jak poznat, jestli mu novinku jádro nabízí.

Zásuvky dřevěné knihovní kartotéky s prázdnými štítky
Zásuvky kartotéky ve veřejné knihovně v Nashue. Foto: MarkBuckawicki, Wikimedia Commons (CC0)

Mezi vytvořením a otevřením je okno

Volání mkdir() adresář založí, ale nevrátí nic, čím by ho program mohl dál držet. Volání open()openat() naopak adresář otevřou, jenže jen takový, který už existuje. Kdo tedy zakládá strom adresářů a chce jim nastavit práva, vlastníka, časy nebo rozšířené atributy, musí to udělat na dva kroky: nejdřív vytvořit, pak otevřít podle jména.

V té mezeře je závod. Jori Koolstra to v popisu klíčové záplaty píše natvrdo: než se program dostane k otevření, může adresář někdo vyměnit za něco jiného. Obejít se to dá jen napůl. Program si smí hned po otevření zavolat fstat() a ověřit, že drží adresář s očekávaným vlastníkem a právy, ale podle Koolstry se to snadno naprogramuje špatně a hlavně to neřekne to podstatné: kdo adresář vytvořil. Adresář založený jiným procesem se stejnými oprávněními je od toho vlastního nerozeznatelný, takže se program nedozví, jestli je jeho a má ho na starost.

Nápad není nový. Stojí v seznamu přání skupiny UAPI, který totéž žádá i pro zařízení a pojmenované roury a jako řešení navrhuje samostatné volání mkdirat_fd().

Příznak, který tři roky vrací jen chybu

Koolstra sáhl po jiné cestě: po kombinaci příznaků, která dnes nedělá nic. O_CREAT říká „vytvoř, když to neexistuje“, O_DIRECTORY říká „uspěj jen tehdy, když je to adresář“. Dohromady si protiřečí a jádro se s nimi nikdy nevypořádalo rozumně. Když cesta neexistovala, vytvořilo obyčejný soubor. Když na ní ležel obyčejný soubor, vrátilo ENOTDIR. Když adresář, vrátilo EISDIR.

V roce 2020 to commit Ala Vira, který se dostal do jádra 5.7, změnilo ještě k horšímu: jádro začalo v prvním případě vracet ENOTDIR a soubor přitom stejně vytvořilo. Tři roky si toho nikdo nevšiml, až v roce 2023 na to narazil Pedro Falcato. Christian Brauner tehdy navrhl, ať se místo návratu ke starému chování udělá to, co dělá NetBSD, tedy vracet EINVAL vždycky. Linus Torvalds podle tehdejšího rozboru na LWN.net souhlasil; v překladu řekl: „Myslím, že můžeme docela dobře předpokládat, že to nikdo doopravdy nepoužívá, a klidně to můžeme pořádně vyčistit.“ Falcato u té příležitosti zjistil, že POSIX nezakazuje ani jedno z těch chování a že FreeBSD kombinaci propustí, pokud adresář existuje.

Brauner změnu zapsal v březnu 2023 do commitu 43b450632676 a do jeho zprávy dal i důvod: kromě úklidu staré chyby to podle něj otevírá možnost dát té kombinaci příznaků v budoucnu něco intuitivnějšího. Právě o tu možnost teď jde.

Co série mění

Nová sémantika je prostá. O_CREAT|O_DIRECTORY provede mkdir a otevře, co vzniklo, a vrátí přitom deskriptor, který adresář drží. Poslední verze série z 12. července má deset záplat, mění 224 řádků v souboru fs/namei.c a přidává test o 201 řádcích. Jedna ze záplat musela předsunout kontrolu, jestli smí program adresář otevřít pro zápis: u obyčejných souborů se dělá až na konci, tady by to znamenalo adresář vytvořit a hned potom volání odmítnout.

Ne všude to bude fungovat. Souborové systémy s vlastním atomickým otevíráním, tedy 9p, Ceph, FUSE, GFS2, NFS, klient SMB a vboxsf, by na to podle Koolstry potřebovaly rozšířit své protokoly. Zatím u nich jádro bit O_CREAT odstraní: existující adresář se otevře, ale u toho, který se měl vytvořit, přijde ENOENT.

Druhé omezení se týká adresářů s příznakem sticky, tedy třeba /tmp, kde smí soubor smazat jen jeho vlastník. Vlastní přepínač pro adresáře série nezavádí a dosavadní pojistka pro obyčejné soubory na ně nedosáhne: podle Koolstry by to zaskočilo správce, kteří s takovou možností nepočítají.

Spor je o to, jak program pozná, že to jádro umí

Spor se nevede o samotnou vlastnost, ale o to, že se zapíná kombinací příznaků, a ne novým voláním. Christoph Hellwig namítá, že jádro neznámé a neověřené kombinace příznaků u open() odjakživa ignoruje, takže se jim nesmí potichu dát význam, pokud to není zpětně slučitelné. Na Braunerovu obranu, že se dosud nic nerozbilo, odpověděl v překladu takto: „Samozřejmě se dosud nic nerozbilo, protože to nikdo nepoužíval. Jakmile tomu dáme význam, začne se to používat a rozbije se to.“

Falcato ten problém rozepsal na příklady. Program by se musel vyrovnat se čtyřmi chováními téže kombinace. Zjistit podle verze jádra, na kterém běží, které z nich platí, přitom nemusí být možné: podniková jádra si vlastnosti backportují na přání, jak se stalo u atomických zápisů v souborovém systému XFS, které vyšly v jádře 6.16 a v SUSE Linux Enterprise Serveru i v Oracle UEK 8 se objevily už v 6.12. K tomu se přidává filtr seccomp, který může neznámé volání odmítnout hláškou o tom, že volání vůbec není.

Brauner, správce vrstvy VFS, to označil za konstrukci, která neobstojí: oprava, po které kombinace vrací tvrdou chybu, je podle něj v dlouhodobě podporovaných jádrech roky a nepřišlo na ni ani jedno hlášení o regresi. Připomněl i tři případy, kdy jádro už jednou obsazený příznak přeznačilo: CLONE_STOPPED na CLONE_NEWCGROUP, CLONE_PID na CLONE_PIDFDO_TMPFILE na kombinaci __O_TMPFILE|O_DIRECTORY. Sám přitom rozlišuje, co je a co není v pořádku: dát význam kombinaci, která se dřív ignorovala, se podle něj nemá, kdežto použít znovu kombinaci, která byla odmítána, ano.

Do jednoho bodu se ti dva nesejdou. Brauner tvrdí, že se změna z roku 2023 dostala do všech dlouhodobě podporovaných jader, Falcato uvádí, že vyšla v jádře 6.4, dostala se zpětně do 6.1, ale ne do 6.2 a 6.3 a ne do 5.15 ani 5.10. Kdo z nich má pravdu, se z diskuse nedozvíme; ani jeden pro své tvrzení odkaz neuvedl.

Koolstra odpovídá, že komu záleží na starých jádrech, ať si po vytvoření ověří, že opravdu drží adresář, nebo ať si prostě vyžádá rozumnou minimální verzi jádra. NeilBrown navrhl kompromis, který by hádání zrušil: přidat do volání openat2() příznak ve smyslu „selži, pokud tuhle kombinaci výslovně neumíš“. Ptá se, proč to neudělat rovnou, když by se tím psaní bezpečné aplikace i výběr jádra staly snadné.

Koolstra proti tomu má dvě námitky, ani jednu ale nepovažuje za zabijáka nápadu. Volání openat2() podle něj blokovala a možná dosud blokuje řada profilů seccomp, mimo jiné v systemd, takže by se k tomu zjišťování nedostaly zrovna ty velké aplikace, jejichž potřeby změny v rozhraní jádra obvykle pohánějí. A za druhé je to podle něj věc slohu: openat2() má otevírat soubory, ne zjišťovat vlastnosti jádra. Sám nadhodil samostatné volání has_feature(2), kterému by se předalo číslo volání a popis vlastnosti.

Kde to stojí

Sloučeno není nic. Brauner 24. července požádal o novou verzi s podporou v openat()openat2(); Koolstra odpověděl, že to už v posílání z 12. července je a jen zapomněl sérii označit jako čtvrtou. Dřív téhož měsíce ho Brauner požádal, ať ze série vyndá nesouvisející opravy, protože zásah do auditu by si vyžádal zapojení lidí od auditu a slučování by zdržel. To se stalo: nová verze má deset záplat místo čtrnácti a audit řeší zvlášť.

Kdo o tom rozhodne, napsal Koolstra sám: je to na Christianovi.

Zdroje: série záplatdiskuse k ní v archivu konference linux-kernel na marc.info, rozbor LWN.net z 27. března 2023, commit 43b450632676 v repozitáři jádra a seznam přání skupiny UAPI.

Diskuse

Zatím tu nikdo nediskutuje. Můžete být první.

Napsat příspěvek

Diskutovat můžete i bez účtu. S registrací se ale příspěvek zveřejní hned a nemusíte pokaždé vyplňovat jméno. Účet už máte? Přihlaste se.

Nezveřejňujeme ho, slouží jen redakci.

Podporuje zápis Texy: **tučně**, *kurzíva*, odrážky, odkazy.

Dál k tématu

Programování

Khronos se ptal přes 400 vývojářů shaderů. Nejvíc jim vadí ladění a profilování

Konsorcium Khronos zveřejnilo výsledky průzkumu mezi lidmi, kteří píšou shadery. Ladění a profilování označilo za jeden ze tří největších problémů 53 procent z nich, tedy o patnáct procentních bodů víc než druhou položku v pořadí. Průzkum si konsorcium udělalo samo a jeho čísla nikdo nezávisle neověřoval.

Programování

Turso přepsal SQLite do Rustu, nad stejným jádrem teď staví Postgres. Zatím jen ze zdroje

Databázový projekt Turso, který vznikl jako přepis SQLite do jazyka Rust, oznámil 16. července, že nad stejným jádrem staví i Postgres. Autoři to popisují jako „LLVM databází“: jedno jádro a víc jazykových rozhraní nad ním. Hotová databáze to zatím není – balíčky ke stažení neexistují a oznámení samo mluví o základu, ne o hotovém produktu.

Programování

Pád ripgrepu ukazuje na chybu v jádře Linuxu. Model od OpenAI odmítl s laděním pomoct

Vývojář nahlásil 26. července, že ripgrep sestavený proti knihovně musl občas spadne při hledání v obřím stromu adresářů. O dva dny později zveřejnil analýzu, podle které za pádem stojí souběh ve správě paměti jádra Linuxu: vlákno přestane vidět bajt, který samo o deset instrukcí dřív zapsalo. Potvrzení od vývojářů jádra zatím není.