Când o aplicație Linux primește nume de fișiere din exterior, calea nu mai este doar un șir de caractere. Poate trece prin directoare schimbate între timp, linkuri simbolice sau puncte de montare pe care programul nu le-a anticipat. Aici apar două probleme clasice: path traversal, când o cale iese din zona permisă, și symlink race, când programul verifică un fișier, dar ajunge să deschidă altul.
openat2() este util tocmai pentru că mută o parte din decizie în kernel. În loc să „sanitizezi” textul căii și să speri că verificarea rămâne valabilă, îi ceri kernelului să aplice reguli de rezolvare prin câmpul resolve din structura open_how.
De ce openat() și O_NOFOLLOW nu sunt suficiente mereu
openat() permite deschiderea unei căi relative față de un descriptor de director, ceea ce este deja mai controlabil decât lucrul direct cu directorul curent al procesului. Totuși, în directoare neîncrezute, problema nu se oprește aici.
O_NOFOLLOW, conform open(2), refuză urmărirea unui link simbolic dacă linkul este componenta finală a căii. Linkurile simbolice din componentele intermediare pot fi urmate în continuare. Pentru un manager de upload, un extractor de arhive sau un broker care deschide fișiere pentru procese izolate, diferența poate fi exact locul în care apare bugul.
De aceea openat2() adaugă un nivel mai explicit: programul setează flags, mode și resolve în open_how, iar kernelul refuză rezolvarea căii când regulile cerute sunt încălcate.
Ce fac opțiunile RESOLVE_*
RESOLVE_BENEATH cere ca rezolvarea căii să rămână sub directorul indicat de dirfd. Dacă o cale încearcă să iasă din acel arbore, apelul eșuează. RESOLVE_IN_ROOT tratează directorul indicat de dirfd ca rădăcină pentru acel apel, nu pentru întregul proces; path_resolution(7) descrie ideea ca efect asemănător cu o rădăcină temporară în timpul rezolvării.
RESOLVE_NO_SYMLINKS refuză linkurile simbolice întâlnite în rezolvare și implică RESOLVE_NO_MAGICLINKS. RESOLVE_NO_MAGICLINKS vizează „magic links”, întâlnite în special în procfs, de exemplu în zone asociate proceselor și descriptorilor. RESOLVE_NO_XDEV refuză traversarea punctelor de montare, util când aplicația nu trebuie să iasă în alt filesystem, bind mount sau volum.
Dacă serviciul depinde de volume montate la pornire, regula RESOLVE_NO_XDEV trebuie gândită împreună cu ordinea serviciilor; contextul operațional se leagă natural de cum setezi corect dependențele systemd între rețea, volume, containere și aplicații pe Linux.
Modelul corect: director permis, căi relative, reguli în kernel
Modelul defensiv este simplu: aplicația deschide directorul permis, păstrează descriptorul și rezolvă căile relative față de el, cu reguli RESOLVE_* potrivite. Astfel, întrebarea „are voie calea să iasă de aici?” este verificată în același apel care deschide fișierul, nu într-o verificare separată ușor de depășit prin schimbări între timp.
Asta reduce riscurile TOCTOU, adică situațiile în care verifici un obiect și folosești altul după o modificare concurentă. Pentru arhitecturi cu brokeri userspace, aceeași disciplină apare și în Seccomp User Notifications în Linux: cum construiești un broker userspace fără capcane TOCTOU.
openat2() nu înlocuiește permisiunile POSIX, AppArmor, SELinux, izolarea containerelor sau politica aplicației. Dacă directorul permis conține deja date pe care utilizatorul nu ar trebui să le vadă, RESOLVE_* nu repară acea problemă. Este o barieră pentru rezolvarea căilor, nu un model complet de autorizare.
Cum tratezi erorile fără să slăbești protecția
openat2(2) documentează erori relevante pentru aceste reguli. ELOOP poate apărea când RESOLVE_NO_SYMLINKS sau RESOLVE_NO_MAGICLINKS întâlnește o componentă interzisă. EXDEV poate apărea când RESOLVE_BENEATH sau RESOLVE_IN_ROOT detectează o încercare de evadare, ori când RESOLVE_NO_XDEV detectează traversarea unui punct de montare.
Aceste rezultate trebuie tratate ca refuzuri de politică, nu ca motive de fallback automat la open() sau openat() fără constrângeri. Un fallback mai permisiv anulează protecția. Pentru vizibilitate defensivă, openat2() poate fi completat cu audit; de exemplu, fanotify în Linux: monitorizare defensivă a accesului la fișiere acoperă partea de observare, dar observarea nu trebuie să înlocuiască refuzul la deschidere.
Întrebări frecvente
openat2() elimină complet symlink races?
Nu. Reduce o clasă importantă de riscuri prin aplicarea constrângerilor în kernel în momentul deschiderii, dar nu înlocuiește permisiunile, izolarea sau auditul aplicației.
De ce nu ajunge O_NOFOLLOW?
O_NOFOLLOW refuză linkul simbolic doar când acesta este componenta finală a căii. Linkurile simbolice din componentele intermediare pot fi urmate în continuare.
Ce înseamnă EXDEV în acest context?
În openat2(), EXDEV poate semnala o încercare de ieșire din rădăcina permisă sau traversarea unui punct de montare interzis de regulile RESOLVE_*.
Este sigur să faci fallback la open() dacă openat2() eșuează?
Nu ca regulă automată. Dacă openat2() refuză calea din motive de politică, un apel mai permisiv poate anula protecția.

















































