EXECVE(2) | Podręcznik programisty Linuksa | EXECVE(2) |
execve - uruchomienie programu
#include <unistd.h>
int execve(const char *filename, char *const
argv[],
char *const envp[]);
execve() wykonuje program wskazywany przez filename. filename musi być albo wykonywalnym plikiem binarnym, albo skryptem zaczynającym się od linii w postaci:
#! interpreter [opcjonalny-parametr]
Szczegóły tego ostatniego przypadku można znaleźć poniżej w rozdziale "Skrypty interpretowane".
argv jest tablicą łańcuchów przekazywanych jako argumenty nowego programu. Zgodnie z konwencją pierwszy z nich powinien zawierać nazwę pliku skojarzonego z wykonywanym programem. envp jest tablicą łańcuchów postaci klucz=wartość, która jest przekazywana jako środowisko do nowego programu. Zarówno argv, jak i envp muszą być zakończone wskaźnikiem pustym (NULL). Tablica argumentów oraz środowisko są dostępne w funkcji main wywoływanego programu, jeżeli jest ona zdefiniowana jako:
int main(int argc, char *argv[], char *envp[])
W razie powodzenia execve() nie powraca, a sekcje tekstu, danych, bss i stos wywołującego procesu są nadpisywane odpowiednimi sekcjami ładowanego programu.
Jeśli obecny program jest śledzony za pomocą ptrace, wysyła się mu SIGTRAP po pomyślnym execve().
Jeżeli plik programu wskazywany przez filename ma ustawiony bit set-user-ID i plik ten jest umieszczony na systemie plików zamontowanym bez opcji nosuid (znacznika MS_NOSUID wywołania mount(2)) oraz proces wywołujący nie jest śledzony (ptrace(2)), to efektywny identyfikator użytkownika procesu wywołującego jest ustawiany na właściciela pliku programu. Podobnie, jeżeli dla pliku programu ustawiony jest bit set-group-ID, to efektywnemu identyfikatorowi grupy procesu wywołującego jest przypisywana grupa pliku programu.
Efektywny identyfikator użytkownika jest kopiowany do saved-set-user-ID; podobnie efektywny identyfikator grupy jest kopiowany do saved-set-group-ID. Kopiowanie odbywa się po zmianie któregokolwiek z efektywnych identyfikatorów związanej z bitami trybu set-user-ID i set-group-ID.
Jeśli program wykonywalny jest skonsolidowany dynamicznie w formacie a.out z bibliotekami dzielonymi, to na początku uruchamiania wywoływany jest konsolidator dynamiczny ld.so(8), który ładuje wszystkie obiekty do pamięci i konsoliduje z nimi program wykonywalny.
Jeżeli program jest skonsolidowany dynamicznie jako ELF, to do załadowania potrzebnych obiektów współdzielonych używany jest interpreter określony w segmencie PT_INTERP. Tym interpreterem jest zazwyczaj /lib/ld-linux.so.2, w wypadku programów skonsolidowanych z glibc2 (zob. ld-linux.so(8)).
Wszystkie atrybuty procesu są zachowywane podczas execve(), z wyjątkiem poniższych:
Atrybuty procesu w liście przedstawionej powyżej są określone w POSIX.1. Następujące specyficzne dla Linuksa atrybuty procesu również nie są zachowywane podczas execve():
Dalsze uwagi:
Skrypt interpretowany jest plikiem tekstowym mającym ustawione prawo do wykonywania. Pierwsza linia tego pliku jest w postaci:
#! interpreter [opcjonalny-parametr]
interpreter mus być poprawną nazwą ścieżki do pliku wykonywalnego. Jeśli argument filename wywołania execve() określa interpreter, to zostanie uruchomiony interpreter z następującymi argumentami:
interpreter [opcjonalny-arg] filename arg...
gdzie arg... jest serią słów, na które wskazuje argument argv wywołania execve(), poczynając od argv[1].
Dla zachowania przenośności na inne systemu optional-arg albo w ogóle nie powinien być podawany, albo powinien być podany jako pojedyncze słowo (nie powinien zawierać spacji); patrz UWAGI poniżej.
Od Linuksa 2.6.28 jądro pozwala, aby interpreterem skryptu również był skrypt. To uprawnienie jest rekurencyjne, aż po czterykroć, tak więc interpreter może być skryptem interpretowanym przez skrypt itd.
Większość implementacji Uniksa narzuca ograniczenia na całkowity rozmiar argumentów linii poleceń (argv) i środowiska (envp) przekazywanych do nowego programu. POSIX.1 pozwala implementacji ogłosić te ograniczenia za pomocą stałej ARG_MAX (albo zdefiniowanej w <limits.h>, albo dostępnej podczas wykonywania programu za pomocą wywołania sysconf(_SC_ARG_MAX)).
W jadrach Linuksa wcześniejszych niż 2.6.23, pamięć używana do przechowywania łańcuchów znaków środowiska i argumentów była ograniczana do 32 stron (zdefiniowane przez stałą jądra MAX_ARG_PAGES). W architekturach mających strony o rozmiarze 4 kB oznaczało to maksymalny rozmiar równy 128 kB.
W jądrze 2.6.23 i późniejszych większość architektur wspiera ograniczenie rozmiaru wywodzące się z miękkiego limitu zasobu RLIMIT_STACK (patrz getrlimit(2)), obowiązującego podczas wywołania execve() (Wyjątek stanowią architektury nie mające jednostki zarządzania pamięcią: przechowują ograniczenie obowiązujące przed wersją jądra 2.6.23). Zmiana ta pozwala programom na posiadanie znacznie większej listy argumentów lub środowiska. Na tych architekturach całkowity rozmiar jest ograniczony do 1/4 dopuszczalnego rozmiaru stosu. (Limit 1/4 zapewnia, że zostanie jakaś przestrzeń na stos dla nowego programu). Od Linuksa 2.6.25 jądro przyjmuje wartość minimalną 32 stron dla tego limitu rozmiaru, tak żeby zagwarantować, że w przypadku gdy RLIMIT_STACK ma niewielką wartość, aplikacje dostaną co najmniej taką przestrzeń na argumenty i środowisko, jaką miały w Linuksie 2.6.23 i wcześniejszych. (Takiej gwarancji nie ma w Linuksach 2.6.23 i 2.6.24). Dodatkowo ograniczeniem na pojedynczy łańcuch znaków są 32 strony (stała jądra MAX_ARG_STRLEN), a maksymalna liczba takich łańcuchów wynosi 0x7FFFFFFF.
Po pomyślnym zakończeniu execve() nie wraca, w wypadku błędu zwracane jest -1 i odpowiednio ustawiane errno.
POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD. POSIX nie opisuje zachowania #!, lecz istnieje ono (z pewnymi odmianami) na innych systemach Uniksowych.
Procesy z ustawionymi znacznikami set-user-ID oraz set-group-ID nie mogą być śledzone za pomocą ptrace(2).
Efekt zamontowania systemu plików nosuid jest różny dla różnych wersji jądra Linuksa: niektóre odmówią uruchomienia programów set-user-ID i set-group-ID, gdy spowodowałoby to udostępnienie użytkownikowi możliwości, którymi w danym momencie nie dysponuje (i zwrócą EPERM), inne po prostu zignorują bity set-user-ID i set-group-ID i pomyślnie wykonają exec().
Pod Linuksem argv i envp może być podany jako NULL. W obu przypadkach, ma to ten sam skutek co podanie danego argumentu jako wskaźnika do listy zawierającej pojedynczy wskaźnik null. Prosimy nie wykorzystywać tej niestandardowej i nieprzenośnej pseudofunkcji! Na większości innych systemów Uniksowych podanie jako argv wartości NULL spowoduje wystąpienie błędu (EFAULT). Część innych systemów Uniksowych traktuje przypadek envp==NULL tak samo jak Linux.
POSIX.1 określa, że wartości zwracane przez sysconf(3) nie powinny się zmieniać przez cały czas życia procesu. Jednakże od wersji 2.6.23 Linuksa zmiana limitu zasobów RLIMIT_STACK powoduje również zmianę wartości zwracanej przez _SC_ARG_MAX, żeby odzwierciedlić fakt, że zmieniły się ograniczenia przestrzeni służącej do przechowywania argumentów linii poleceń i zmiennych środowiska.
W większości sytuacji gdy execve() zawiedzie, kontrola powraca do oryginalnego obrazu wykonywalnego, a wywołujący execve() może następnie obsłużyć błąd. Jednak są (rzadkie) przypadki (zwykle przy braku zasobów), gdy błąd może wystąpić w momencie bez powrotu: oryginalny obraz wykonywalne został podzielony, a nie można całkowicie zbudować nowego obrazu. W takich sytuacjach jądro zabija proces sygnałem SIGKILL.
Maksymalna długość pierwszego wiersza skryptu interpretera wynosi 127 znaków.
Semantyka argumentu optional-arg skryptu interpretera różni się pomiędzy implementacjami. Pod Linuksem cały łańcuch znaków występujący po nazwie interpretera jest przekazywany jako pojedynczy argument. Jednakże inne systemy zachowują się inaczej. Niektóre systemy traktują pierwszy znaku białej spacji jako znak kończący optional-arg. Na innych systemach skrypt interpretera może przyjmować wiele argumentów i białe znaki optional-arg służą do ich rozdzielania.
Linux ignoruje bity set-user-ID i set-group-ID dla skryptów.
Poniżej znajduje się bardziej szczegółowy opis błędu EAGAIN, który może wystąpić (od Linuksa 3.1) przy wywoływaniu execve().
Błąd EAGAIN może wystąpić, gdy wywołanie poprzedzające setuid(2), setreuid(2) lub setresuid(2) spowodowało, że rzeczywisty ID użytkownika procesu zmienił się i ta zmiana doprowadziła do wyczerpania jego limitu zasobów RLIMIT_NPROC (tzn. liczba procesów należących do nowego rzeczywistego UID przekroczy limit zasobów). W wersjach Linuksa od 2.6.0 do 3.0 powodowało to niepowodzenie wywołania set*uid(). Przed wersja 2.6 limit zasobów nie był nakładany w przypadku procesów zmieniających swój identyfikator użytkownika.
Od Linuksa 3.1, opisana sytuacja nie powoduje już niepowodzenia wywołania set*uid(), ponieważ zbyt często prowadziło to do dziur bezpieczeństwa, gdy nieprawidłowo napisane programy nie sprawdzały statusu zakończenia i przyjmowały, że—jeśli wywołujący ma uprawnienia roota—wywołanie zawsze powiedzie się. Obecnie wywołania set*uid() poprawnie zmieniają rzeczywisty UID, lecz jądro ustawia wewnętrzną flagę PF_NPROC_EXCEEDED, wskazując że przekroczono limit zasobów RLIMIT_NPROC. Jeśli flaga PF_NPROC_EXCEEDED jest ustawiona, a limit zasobów jest wciąż przekroczony w trakcie kolejnego wywołania execve(), to wywołanie to zakończy się z błędem EAGAIN. Ta logika jądra zapewnia, że limit zasobów RLIMIT_NPROC jest wciąż wymuszony dla zwykłej pracy demonów uprzywilejowanych —przykładem jest fork(2) + set*uid() + execve().
Jeśli jednak limit zasobów nie był już przekroczony w trakcie wywołania execve() (ponieważ zakończyły się inne procesy należące do tego rzeczywistego UID pomiędzy wywołaniami set*uid() i execve()), to wywołanie execve() powiedzie się, a jądro usunie flagę procesu PF_NPROC_EXCEEDED. Flaga jest usuwana również wówczas, gdy kolejne wywołanie do fork(2) przez ten proces powiedzie się.
W Uniksie V6 lista argumentów wywołania exec() była kończona 0, podczas gdy lista argumentów funkcji main była kończona -1. Dlatego lista argumentów przekazana do main nie mogła być bezpośrednio użyta w wywołaniu exec(). Od Uniksa V7 obie te wartości są NULL.
Następujący program jest zaprojektowany do wykonania przez drugi program przedstawiony poniżej. Wyświetla swoje argumenty uruchomienia po jednym w wierszu.
/* myecho.c */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) {
int j;
for (j = 0; j < argc; j++)
printf("argv[%d]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS); }
Tego programu można użyć do uruchomienia
programu podanego w argumencie linii poleceń:
/* execve.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) {
char *newargv[] = { NULL, "witaj", "świecie", NULL };
char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Użycie: %s <plik-do-uruchomienia>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() wraca tylko w przypadku błędu */
exit(EXIT_FAILURE); }
Możemy użyć drugiego programu do uruchomienia pierwszego:
$ cc myecho.c -o myecho $ cc execve.c -o execve $ ./execve ./myecho argv[0]: ./myecho argv[1]: witaj argv[2]: świecie
Możemy także użyć tych programów do pokazania używania interpretera skryptu. Aby to zrobić, tworzymy skrypt, którego "interpreterem" jest nasz program myecho:
$ cat > script #!./myecho script-arg ^D $ chmod +x script
Następnie używamy naszego programu do wykonania skryptu:
$ ./execve ./script argv[0]: ./myecho argv[1]: script-arg argv[2]: ./script argv[3]: witaj argv[4]: świecie
chmod(2), execveat(2), fork(2), ptrace(2), execl(3), fexecve(3), getopt(3), system(3), credentials(7), environ(7), path_resolution(7), ld.so(8)
Angielska wersja tej strony pochodzi z wydania 4.07 projektu Linux man-pages. Opis projektu, informacje dotyczące zgłaszania błędów, oraz najnowszą wersję oryginału można znaleźć pod adresem https://www.kernel.org/doc/man-pages/.
Autorami polskiego tłumaczenia niniejszej strony podręcznika man są: Przemek Borys (PTM) <pborys@dione.ids.pl>, Andrzej Krzysztofowicz (PTM) <ankry@mif.pg.gda.pl>, Robert Luberda <robert@debian.org> i Michał Kułach <michal.kulach@gmail.com>.
Polskie tłumaczenie jest częścią projektu manpages-pl; uwagi, pomoc, zgłaszanie błędów na stronie http://sourceforge.net/projects/manpages-pl/. Jest zgodne z wersją 4.07 oryginału.
2016-03-15 | Linux |