Scroll to navigation

execve(2) System Calls Manual execve(2)

NAZWA

execve - wykonuje program

BIBLIOTEKA

Standardowa biblioteka C (libc, -lc)

SKŁADNIA

#include <unistd.h>
int execve(const char *pathname, char *const _Nullable argv[],
           char *const _Nullable envp[]);

OPIS

execve() wykonuje program wskazywany przez pathname. Program, który jest aktualnie wykonywany przez proces wywołujący jest zastępowany nowym programem, z nowo zainicjowanym stosem, kopcem i (zainicjowanymi i niezainicjowanymi) segmentami danych.

pathname musi być albo wykonywalnym plikiem binarnym, albo skryptem zaczynającym się od wiersza w postaci:


#!interpreter [opcjonalny-parametr]

Szczegóły tego ostatniego przypadku można znaleźć poniżej w rozdziale „Skrypty interpretowane”.

argv jest tablicą wskaźników do łańcuchów przekazywanych jako argumenty wiersza poleceń nowego programu. Zgodnie z konwencją, pierwszy z nich (tj. argv[0]) powinien zawierać nazwę pliku skojarzonego z wykonywanym programem. Tablica argv musi być zakończone wskaźnikiem NULL (dlatego w nowym programie, argv[argc] będzie wynosić NULL).

envp jest tablicą wskaźników do łańcuchów, zgodnie z konwencją postaci klucz=wartość, która jest przekazywana jako środowisko do nowego programu. Tablica envp musi być zakończona wskaźnikiem pustym (NULL).

Niniejszy podręcznik opisuje detale linuksowego wywołania systemowego; aby zapoznać się z przeglądem nomenklatury oraz wieloma, często preferowanymi, standardowymi wariantami niniejszej funkcji udostępnianymi przez libc, w tym przeszukującymi zmienną środowiskową PATH, zob. exec(3).

Argument wektora i środowiska może być dostępny z głównej funkcji nowego programu, gdy zostanie zdefiniowany jako:


int main(int argc, char *argv[], char *envp[])

Proszę jednak zauważyć, że użycie trzeciego argumentu głównej funkcji nie jest określone normą POSIX.1; zgodnie z POSIX.1, dostęp do środowiska powinien następować za pomocą zewnętrznej zmiennej environ(7).

W razie powodzenia execve() nie powraca, a sekcje tekstu, zainicjowanych danych, niezainicjowanych danych (bss) i stos wywołującego procesu są nadpisywane zgodnie z zawartością nowo ładowanego programu.

Jeśli obecny program jest śledzony za pomocą ptrace, wysyła się mu SIGTRAP po pomyślnym execve().

Jeżeli plik programu, do którego odnosi się filename, ma ustawiony bit set-user-ID, 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.

Wyżej wymienione przekształcenia efektywnego identyfikatora nie są przeprowadzane (tzn. bity set-user-ID i set-group-ID są ignorowane) jeśli dowolny z poniższych warunków jest prawdziwy:

dla wywołującego wątku ustawiony jest atrybut no_new_privs (zob. prctl(2));
przedmiotowy system plików jest zamontowany z nosuid (znacznik MS_NOSUID dla mount(2)) lub
wywołujący proces jest śledzony za pomocą ptrace.

Przywileje pliku programu (zob. capabilities(7)) są również ignorowane, jeśli dowolny z powyższych warunków jest prawdziwy.

Efektywny identyfikator użytkownika jest kopiowany do zapisanego set-user-ID; podobnie efektywny identyfikator grupy jest kopiowany do zapisanego 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.

Rzeczywisty identyfikator procesu i rzeczywisty identyfikator grupy, podobnie jak uzupełniające identyfikatory grupy, pozostają bez zmian przy wywołaniu do execve().

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)).

Wpływ na atrybuty procesu

Wszystkie atrybuty procesu są zachowywane podczas execve(), z wyjątkiem poniższych:

Ustawienia obsługi sygnałów, które są przechwytywane, są zmieniane na wartości domyślne (signal(7)).
Alternatywny stos sygnałów nie jest zachowywany (sigaltstack(2)).
Mapowania pamięci nie są zachowywane (mmap(2))
Dołączone segmenty pamięci dzielonej Systemu V są odłączane (shmat(2)).
Regiony pamięci dzielonej POSIX są odmapowane (shm_open(3)).
Otwarte kolejki komunikatów POSIX są zamykane (mq_overview(7)).
Otwarte semafory nazwane POSIX są zamykane (mq_overview(7)).
Timery POSIX nie są zachowywane (timer_create(2)).
Otwarte strumienie katalogów są zamykane (opendir(3)).
Blokady pamięci nie są zachowywane (mlock(2), mlockall(2)).
Zarejestrowanie funkcje wykonywanych po zakończeniu procesu nie są zachowywane (atexit(3), on_exit(3)).
Środowisko zmiennoprzecinkowe jest ustawiane na domyślne (patrz fenv(3)).

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():

Atrybut „dumpable” („zrzucalny”) jest ustawiany na wartość 1, chyba że wykonywany jest program: z set-user-ID, z set-group-ID lub z przywilejami (ang. capabilities); wówczas atrybut ten może być zresetowany na wartość z /proc/sys/fs/suid_dumpable, w przypadkach opisanych odnośnie PR_SET_DUMPABLE w podręczniku prctl(2). Proszę zauważyć, że zmiany atrybutu „dumpable” mogą spowodować zmianę własności plików w katalogu procesu /proc/pid na root:root, zgodnie z opisem w podręczniku proc(5).
Znacznik PR_SET_KEEPCAPS prctl(2) jest czyszczony.
(Od Linuksa 2.4.36 / 2.6.23) Jeśli wykonywany program ma ustawiony bit set-user-ID lub set-group-ID, to jest czyszczony znacznik PR_SET_PDEATHSIG sygnału śmierci rodzica ustawiony przez prctl(2).
Nazwa procesu ustawiona przez PR_SET_NAME z prctl(2) (i wyświetlana przez ps -o comm) jest ustawiana na nazwę nowego pliku wykonywalnego.
Znacznik SECBIT_KEEP_CAPS w securebits jest czyszczony. Patrz capabilities(7).
Sygnał zakończenia jest ustawiany na SIGCHLD (patrz clone(2)).
Tablica deskryptora plików nie jest dzielona, co anuluje działanie flagi CLONE_FILES clone(2).

Dalsze uwagi:

Wszystkie wątki oprócz wątku wywołującego są niszczone podczas execve(). Zatrzaski (muteksy), zmienne warunkowe i inne obiekty pthreads nie są zachowywane.
Odpowiednik setlocale(LC_ALL, "C") jest wykonywany po uruchomieniu programu.
POSIX.1 określa, że ustawienie procedur obsługi sygnału na ignorowanie lub na wartość domyślną jest pozostawiane bez zmian. POSIX.1 przewiduje jeden wyjątek od tej reguły: jeśli SIGCHLD jest ignorowany, to implementacja może albo nie zmienić tego ustawienia, albo przestawić je na wartość domyślną; Linux robi to pierwsze.
Wszystkie asynchroniczne operacje wejścia/wyjście są anulowane (aio_read(3), aio_write(3)).
Sposób obsługi przywilejów procesu podczas execve() opisano w capabilities(7).
Domyślnie deskryptory plików pozostają otwarte po execve(). Deskryptory plików oznaczone jako „zamknij-przy-wykonaniu” są zamykane, patrz opis FD_CLOEXEC w fcntl(2). (Jeśli deskryptor pliku zostanie zamknięty, to zwolnione zostaną wszystkie blokady rekordów dotyczące pliku związanego z tym deskryptorem. Szczegóły można znaleźć w fcntl(2)). POSIX.1 mówi, że jeżeli deskryptory plików 0, 1 i 2 zostałyby zamknięte po pomyślnym wykonaniu execve(), a proces uzyskałby przywileje z powodu ustawionego na wykonywanym pliku bitu trybu set-user-ID lub set-group-ID, to system może otworzyć bliżej nieokreślony plik dla każdego z tych deskryptorów plików. Jako zasadę należy przyjąć, że żaden przenośny program, uprzywilejowany czy nie, nie może zakładać, że te trzy deskryptory plików będą zamknięte po execve().

Skrypty interpretowane

Skrypt interpretowany jest plikiem tekstowym mającym ustawione prawo do wykonywania. Pierwszy wiersz tego pliku jest w postaci:


#!interpreter [opcjonalny-parametr]

interpreter mus być poprawną nazwą ścieżki do pliku wykonywalnego.

Jeśli argument pathname wywołania execve() określa interpreter, to zostanie uruchomiony interpreter z następującymi argumentami:


interpreter [opcjonalny-arg] pathname arg...

gdzie pathname jest ścieżką pliku podanego jako pierwszy argument execve(), a arg... jest zestawem słów, na które wskazuje argument argv execve(), zaczynając od argv[1]. Proszę zauważyć, że nie da się pozyskać argv[0] przekazanego do wywołania execve().

Dla zachowania przenośności na inne systemy, 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.

Ograniczenia rozmiaru argumentów i środowiska

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)).

Przed Linuksem 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 Linuksie 2.6.23 i późniejszych, większość architektur obsługuje 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 Linuksem 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). Dodatkowo, całkowity rozmiar jest ograniczony do 3/4 wartości stałej jądra _STK_LIM (8 MiB). Od Linuksa 2.6.25 jądro przyjmuje również 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.22 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.

WARTOŚĆ ZWRACANA

Po pomyślnym zakończeniu execve() nie wraca, w wypadku błędu zwracane jest -1 i ustawiane errno wskazując błąd.

BŁĘDY

Całkowita liczba bajtów środowiska (envp) i listy argumentów (argv) jest za duża.
Brak praw do przeszukiwania dla składnika ścieżki pathname lub ścieżki interpretera skryptu (patrz także path_resolution(7)).
Plik lub interpreter skryptu nie jest zwykłym plikiem.
Brak praw wykonywania dla pliku, skryptu lub intepretera ELF.
System plików jest zamontowany jako noexec.
Po zmianie swojego rzeczywistego UID za pomocą jednego z wywołań set*uid(), wywołujący był – i wciąż jest – powyżej swojego limitu zasobów RLIMIT_NPROC (zob. setrlimit(2)). Więcej informacji o tym błędzie znajduje się w rozdziale UWAGI.
pathname lub jeden ze wskaźników w wektorach argv lub envp wskazuje poza dostępną dla użytkownika przestrzeń adresową.
Plik wykonywalny w formacie ELF ma więcej niż jeden segment PT_INTERP (tzn. ma więcej niż jeden interpreter).
Wystąpił błąd wejścia/wyjścia.
Intepreter ELF jest katalogiem.
Nie został rozpoznany format interpretera ELF.
Podczas rozwiązywania pathname, nazwy skryptu lub interpretera ELF napotkano zbyt wiele dowiązań symbolicznych.
Osiągnięto maksymalny limit rekurencji podczas intepretacji rekurencyjnego skryptu (zob. pow. "Skrypty interpretowane"). Przed Linuksem 3.8 w takim wypadku występował błąd ENOEXEC.
Zostało osiągnięte ograniczenie na liczbę otwartych deskryptorów plików dla procesu.
Ścieżka pathname jest zbyt długa.
Zostało osiągnięte systemowe ograniczenie na całkowitą liczbę otwartych plików.
Plik pathname, skrypt lub interpreter ELF nie istnieje.
Nie rozpoznano formatu pliku binarnego, plik ten jest skompilowany dla innej architektury albo wystąpił jakiś inny błąd formatu pliku, który powoduje, że program nie może być uruchomiony.
Brak pamięci jądra.
Składowa ścieżki pathname, ścieżki skryptu lub ścieżki interpretera ELF nie jest katalogiem.
System plików jest zamontowany jako nosuid, użytkownik nie jest administratorem, a plik ma ustawiony bit set-user-ID lub set-group-ID.
Proces jest śledzony (trace), użytkownik nie jest superużytkownikiem, a plik ma ustawiony bit set-user-ID lub set-group-ID.
Aplikacje ślepe na przywileje nie pozyskają pełnego zestawu dozwolonych przywilejów przyznanego przez plik wykonywalny. Zob. capabilities(7).
Podany plik wykonywalny był otwarty do zapisu przez jeden lub więcej procesów.

WERSJE

POSIX nie opisuje zachowania #!, lecz istnieje ono (z pewnymi odmianami) na innych systemach Uniksowych.

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.

Skrypty interpretowane

Jądro narzuca maksymalną długość tekstu następującego po znakach „#!” na początku skryptu; znaki wykraczające poza limit są ignorowane. Przed Linuksem 5.1, limit wynosi 127 znaków. Od Linuksa 5.1, limit wynosi 255 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 (podobnie jest większość innych współczesnych systemów uniksowych) ignoruje bity set-user-ID i set-group-ID dla skryptów.

STANDARDY

POSIX.1-2008.

HISTORIA

POSIX.1-2001, SVr4, 4.3BSD.

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.

UWAGI

Można czasami przeczytać, że execve() (i powiązane funkcje opisane w exec(3)) są opisywane jako „wykonujące nowy proces” (lub podobnie). Jest to stwierdzenie bardzo mylące: nie występuje tu nowy proces; wiele atrybutów procesu wywołującego pozostaje niezmienionych (w szczególności jego identyfikator procesu). Wszystko, co robi execve(), to zaaranżowanie wykonania nowego programu przez istniejący proces (proces wywołujący).

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 Linux: 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().

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 SIGSEGV (SIGKILL do Linuksa 3.17).

execve() i EAGAIN

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). Od Linuksa 2.6.0 do Linuksa 3.0 powodowało to niepowodzenie wywołania set*uid(). Przed Linuksem 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ę.

PRZYKŁADY

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[])
{

for (size_t j = 0; j < argc; j++)
printf("argv[%zu]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS); }

Tego programu można użyć do uruchomienia programu podanego w argumencie wiersza poleceń:


/* execve.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{

static char *newargv[] = { NULL, "witaj", "świecie", NULL };
static char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Użycie: %s <plik-do-wykon>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() powraca tylko przy błędzie */
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

ZOBACZ TAKŻE

chmod(2), execveat(2), fork(2), get_robust_list(2), ptrace(2), exec(3), fexecve(3), getauxval(3), getopt(3), system(3), capabilities(7), credentials(7), environ(7), path_resolution(7), ld.so(8)

TŁUMACZENIE

Autorami polskiego tłumaczenia niniejszej strony podręcznika są: Przemek Borys <pborys@dione.ids.pl>, Andrzej Krzysztofowicz <ankry@green.mf.pg.gda.pl>, Robert Luberda <robert@debian.org> i Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.

3 maja 2023 r. Linux man-pages 6.05.01