Linux
2019-05-09
man-pages-ru
Russian man pages from the Linux Documentation Project
manpages-dev
Manual pages about using GNU/Linux for development
man-pages
Linux kernel and C library user-space interface documentation
ИМЯ
execve - выполнить программу
ОБЗОР
#include <unistd.h>
int execve(const char *pathname, char *const argv[],
char *const envp[]);
char *const envp[]);
ОПИСАНИЕ
Вызов execve выполняет программу, задаваемую pathname. При этом программа, выполняющаяся вызвавшим процессом, замещается новой программой, заново инициализируется стек, куча и сегменты данных (инициализированные и не инициализированные).
Также игнорируются мандаты файла программы (смотрите capabilities(7)), если что-то из вышеперечисленного истинно.
В POSIX.1 определён список сохраняемых свойств процесса. Следующие свойства процесса, имеющиеся только в Linux, также не сохраняются при execve():
Также стоит учитывать следующее:
В pathname должно быть указано имя двоичного исполняемого файла или сценарий, начинающийся со строки вида:
#!интерпретатор [необязательный параметр]
Подробней о сценариях написано далее в «Интерпретируемые сценарии».
argv — это массив строковых параметров, передаваемых новой программе. По соглашению, в первой строке (т. е., argv[0]) должно содержаться имя файла, относящееся к запускаемой программе. envp — это массив строк в формате ключ=значение, которые передаются новой программе в качестве окружения (environment). Оба массива argv и envp завершаются указателем null.
К массиву параметров и окружению можно обратиться из вызываемой программой главной функции, если она определена как:
int main(int argc, char *argv[], char *envp[])
Однако заметим, что использование третьего аргумента главной функции не определено в POSIX.1; согласно POSIX.1, окружение должно быть доступно через внешнюю переменную environ(7).
При успешном выполнении execve() управление не возвращается, а код, инициализированные данные, неинициализированные данные (bss) и стек вызвавшего процесса перезаписываются содержимым загруженной программы.
Если текущая программа выполнялась под управлением ptrace, то после успешного вызова execve() ей посылается сигнал SIGTRAP.
Если у файла программы, на который ссылается pathname, установлен бит set-user-ID, то фактический идентификатор пользователя вызывающего процесса меняется на идентификатор владельца файла программы. Точно также, если на файле программы установлен бит set-group-ID, то фактический идентификатор группы вызывающего процесса становится равным группе, которой принадлежит файл программы.
Вышеупомянутые преобразования эффективных IDs не выполняются (т. е., биты set-user-ID и set-group-ID игнорируются), если что-либо из следующего истинно:
* | установлен атрибут no_new_privs для вызывающей нити (смотрите prctl(2)); |
* | подлежащая файловая система смонтирована с nosuid (флаг MS_NOSUID для mount(2)); или |
* | вызывающий процесс выполняется под контролем ptrace. |
Фактический идентификатор пользователя процесса копируется в сохранённый идентификатор пользователя (set-user-ID), также фактический идентификатор группы копируется в сохранённый идентификатор группы (set-group-ID). Это копирование выполняется после изменения любого фактического идентификатора, которое происходит из-за выставленных бит режима set-user-ID и set-group-ID.
Реальные UID и GID процесса, а также его дополнительные ID групп не изменяются при вызове execve().
Если исполняемый файл является динамически-скомпонованным файлом в формате a.out, содержащим заглушки для динамических библиотек, то в начале выполнения этого файла вызывается динамический компоновщик Linux ld.so(8), который начинает выполнение с загрузки общих объектов в память и компонует их с исполняемым файлом.
Если исполняемый файл является динамически компонуемым файлом в формате ELF, то для загрузки необходимых общих объектов используется интерпретатор, указанный в сегменте PT_INTERP. Для программ, скомпонованных с glibc, обычно это /lib/ld-linux.so.2 (смотрите ld-linux.so(8)).
При вызове execve() сохраняются все свойства процесса, за исключением:
* | Значения обработчиков всех захватываемых сигналов сбрасываются в значения по умолчанию (signal(7)). |
* | Любой альтернативный стек сигнала не сохраняется (sigaltstack(2)). |
* | Проецирование памяти не сохраняется (mmap(2)). |
* | Подключённые общие сегменты памяти System V отключаются (shmat(2)). |
* | Области общей памяти POSIX становятся неспроецированными (shm_open(3)). |
* | Открытые дескрипторы в очереди сообщений POSIX закрываются (mq_overview(7)). |
* | Все открытые именные семафоры POSIX закрываются (sem_overview(7)). |
* | Таймеры POSIX не сохраняются (timer_create(2)). |
* | Все открытые потоки каталогов (directory streams) закрываются (opendir(3)). |
* | Блокировки памяти не сохраняются (mlock(2), mlockall(2)). |
* | Обработчики завершения работы (exit handlers) не сохраняются (atexit(3), on_exit(3)). |
* | Окружения плавающей точки сбрасываются в настройки по умолчанию (fenv(3)). |
* | Устанавливается флаг PR_SET_DUMPABLE (prctl(2)), если выполняемая программа не имеет установленных бит set-user-ID или set-group-ID; в противном случае он очищается. |
* | Флаг PR_SET_KEEPCAPS (prctl(2)) очищается. |
* | (Начиная с Linux 2.4.36 / 2.6.23) Если выполняется программа с установленным битом set-user-ID или set-group-ID, то сигнал о смерти родителя, установленный prctl(2) с флагом PR_SET_PDEATHSIG, очищается. |
* | Имя процесса, установленное через prctl(2) PR_SET_NAME (и отображаемое ps -o comm), изменяется на имя нового исполняемого файла. |
* | Флаг SECBIT_KEEP_CAPS securebits очищается. Смотрите capabilities(7). |
* | Сигнал завершения (termination signal) устанавливается в SIGCHLD (clone(2)). |
* | Таблица файловых дескрипторов не является общей, отменяется действие флага CLONE_FILES у clone(2). |
* | Все нити (threads), отличные от вызывающей, уничтожаются execve(). Мьютексы, условные переменные и другие объекты pthreads не сохраняются. |
* | При запуске программы выполняется эквивалент setlocale(LC_ALL, "C"). |
* | В POSIX.1 указано, что действия по отношению к любым игнорируемым или имеющим настройку по умолчанию сигналам, остаются неизменными. В POSIX.1 есть одно исключение: если SIGCHLD игнорируется, то реализация может оставить обработку сигнала (disposition) неизменной или вернуть настройку по умолчанию; в Linux используется первое. |
* | Все ожидающие выполнения асинхронные операции ввода-вывод отменяются (aio_read(3), aio_write(3)). |
* | Как происходит обработка мандатов (capabilities) при вызове execve(), см. capabilities(7). |
* | По умолчанию, после execve() файловые дескрипторы остаются открытыми. Файловые дескрипторы, помеченные как close-on-exec (закрывать при запуске), закрываются; смотрите описание FD_CLOEXEC в fcntl(2) (если файловый дескриптор закрыт, это приводит к освобождению всех имеющихся блокировок, полученных на соответствующий файл данным процессом. Подробней смотрите fcntl(2)). В POSIX.1 сказано, что если бы файловые дескрипторы 0, 1 и 2 были закрыты после успешного вызова execve(), и процесс получил бы привилегии из-за установленных битов режима set-user-ID или set-group_ID на исполняемом файле, то система смогла бы открыть произвольный файл для каждого из этих дескрипторов. Считается, что переносимая программа, с привилегиями или без, не может рассчитывать, что эти три файловых дескриптора будут оставаться закрытыми после execve(). |
Интерпретируемые сценарии
Интерпретируемый сценарий — это текстовый файл, у которого установлен бит выполнения и первая строка имеет вид:
#!интерпретатор [необязательный параметр]
В поле интерпретатор должно быть указано имя файла запуска.
Если в аргументе pathname для execve() указан интерпретируемый сценарий, то интерпретатор будет вызван со следующими параметрами:
интерпретатор [необязательный параметр] pathname параметр…
где pathname — абсолютный путь к файлу, указанному в первом аргументе execve() и параметр... — последовательность слов, указываемых аргументом argv в execve() начиная с argv[1]. Заметим, что нельзя получить argv[0], переданный в вызов execve().
В целях переносимости, необязательный параметр должен быть или пустым, или задаваться одним словом (т.е., не должен содержать пробельных символов); см. ЗАМЕЧАНИЯ далее.
Начиная с Linux 2.6.28 ядро позволяет интерпретатору сценария самому быть сценарием. Это разрешение рекурсивно (до четырёх раз), поэтому сценарий может быть сценарием, который интерпретируется сценарием и т. д.
Ограничения на размер параметров и окружения
Большинство реализаций UNIX накладывает некоторые ограничения на полный размер параметра командной строки (argv) и окружения (envp), которые можно передать новой программе. POSIX.1 позволяет реализации объявить это ограничение через константу ARG_MAX (определённую в <limits.h> или сделать её доступной во время выполнения через вызов sysconf(_SC_ARG_MAX)).
В ядре Linux до версии 2.6.23 размер памяти, используемый для хранения окружения и строк параметров, был ограничен 32 страницами (определялся ядерной константой MAX_ARG_PAGES). На архитектурах с 4-КиБ размером страницы это давало максимальный размер в 128 КиБ.
Начиная с ядра версии 2.6.23, большинство архитектур поддерживают предельный размер, высчитываемый от мягкого ограничения ресурса RLIMIT_STACK (см. getrlimit(2)), который действует во время вызова execve(). (Исключение составляют архитектуры без механизма управления памятью: в них ограничение рассчитывается как и до версии 2.6.23.) Это изменение позволяет программам иметь больший список параметров и/или окружения. Для этих архитектур полный размер ограничен до 1/4 разрешённого размера стека. (Накладываемое ограничение в 1/4 позволяет новой программе всегда иметь некоторое пространство под стек.) Кроме того, полный размер ограничен 3/4 значения ядерной константы _STK_LIM (8 мибибайт). Начиная с Linux версии 2.6.25, ядро также отводит нижние 32 страницы для этого предельного размера, поэтому, даже когда RLIMIT_STACK задан слишком низко, приложения гарантированно получат, по крайней мере, столько же пространства под параметры и окружение, сколько бы они получили при работе с Linux 2.6.23 и ранее. (Это гарантия не обеспечивалась в Linux 2.6.23 и 2.6.24.) Также, размер строки ограничен 32 страницами (ядерная константа MAX_ARG_STRLEN), а максимальное число строк может быть 0x7FFFFFFF.
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При успешном выполнении execve() не возвращает управление. В случае ошибки возвращается -1, а errno устанавливается в соответствующее значение.
ОШИБКИ
E2BIG | Слишком большое общее количество байт для окружения (envp) и списка параметров (argv). |
EACCES | В одном из каталогов префикса pathname или интерпретатора не разрешён поиск (смотрите также path_resolution(7)). |
EACCES | Файл или интерпретатор не являются обычным файлом. |
EACCES | Не установлен бит выполнения на файле или сценарии или интерпретаторе ELF. |
EACCES | Файловая система смонтирована с noexec. |
EAGAIN (начиная с Linux 3.1) | |
Из-за изменения реального UID одним из вызовов set*uid() ранее, вызывающий всё ещё превышает ограничитель ресурса RLIMIT_NPROC (смотрите setrlimit(2)). Подробное объяснение этой ошибки смотрите в ЗАМЕЧАНИЯХ. | |
EFAULT | Значение pathname или один из указателей в векторах argv или envp указывает за пределы доступного адресного пространства. |
EINVAL | Исполняемый ELF-файл содержит более одного сегмента PT_INTERP (т.е., в нём указано более одного интерпретатора). |
EIO | Произошла ошибка ввода-вывода. |
EISDIR | Интерпретатор ELF является каталогом. |
ELIBBAD | |
Не распознан формат интерпретатора ELF. | |
ELOOP | Во время определения pathname, имени сценария или интерпретатора ELF встретилось слишком много символьных ссылок. |
ELOOP | Достигнут предел количества рекурсий при интерпретации сценария (смотрите «Интерпретируемые сценарии» выше). До Linux 3.8 для этого случая возвращалась ошибка ENOEXEC. |
EMFILE | Было достигнуто ограничение по количеству открытых файловых дескрипторов на процесс. |
ENAMETOOLONG | |
Слишком длинное значение аргумента pathname. | |
ENFILE | Достигнуто максимальное количество открытых файлов в системе. |
ENOENT | Файл pathname, сценарий или интерпретатор ELF не существует, или не найдена динамическая библиотека, необходимая для файлового интерпретатора. |
ENOEXEC | |
Не распознан формат исполняемого файла, он не подходит для архитектуры, или имеет ошибки в формате, из-за чего не может быть выполнен. | |
ENOMEM | Недостаточное количество памяти ядра. |
ENOTDIR | |
Компонент пути в pathname, сценарии или интерпретаторе ELF в действительности не является каталогом. | |
EPERM | Файловая система смонтирована с nosuid, пользователь не является суперпользователем, а на файле установлен бит set-user-ID или set-group-ID. |
EPERM | Над процессом выполняется трассировка, пользователь не имеет прав суперпользователя, а у файла установлен бит set-user-ID или set-group-ID. |
EPERM | Приложение «с недоработанными мандатами» (capability-dumb) не получило бы полный набор ограничивающих мандатов, разрешаемых исполняемым файлом. Смотрите capabilities(7). |
ETXTBSY | |
Заданный исполняемый файл был открыт на запись одним или более процессов. |
СООТВЕТСТВИЕ СТАНДАРТАМ
POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD. В POSIX не описано поведение #!, но это существует (в нескольких вариантах) в других системах UNIX.
ЗАМЕЧАНИЯ
Иногда, про execve() (и подобные функции, описанные в exec(3)) говорят, что он «выполняет новый процесс». Это крайне некорректная фраза — не появляется нового процесса; много атрибутов вызывающего процесса остаются неизменными (в частности, его PID). Всё, что делает execve(2), это перестраивает существующий процесс (вызывавший процесс) под выполнение новой программы.
Над процессами с установленными set-user-ID и set-group-ID не может выполняться ptrace(2).
Результат работы при монтировании файловой системы с параметром nosuid различается в разных версиях ядра Linux: некоторые будут отказывать в запуске исполняемых файлов с установленными битами set-user-ID и set-group-ID, если это дало бы пользователю больше прав чем уже есть (и возвращать EPERM), другие просто проигнорируют биты set-user-ID и set-group-ID и успешно выполнят exec().
В Linux значения argv и envp могут быть равны NULL. В обоих случаях, это работает также, как если аргумент бы содержал указатель на список с единственным указателем null. Не пользуйтесь преимуществом данной нестандартной и непереносимой возможностью! В многих других системах UNIX указание argv равным NULL приводит к ошибке (EFAULT). Некоторые другие системы UNIX при envp==NULL работают также как Linux.
В POSIX.1 указано, что значения, возвращаемые sysconf(3), должны быть неизменны в течении существования процесса. Однако, начиная с версии Linux 2.6.23, если изменяется ограничение ресурса RLIMIT_STACK, то значение, возвращаемое для _SC_ARG_MAX, также будет изменено, чтобы отразить, что ограничение на пространство для хранения параметров командной строки и окружения было изменено.
В большинстве случаев отказа execve() управление возвращается в первоначально исполняемый образ и вызывающий execve() может обработать ошибку. Однако в (редких) случаях (обычно вызванных отсутствием ресурсов), ошибка может возникнуть после точки невозврата: первоначально исполняемый образ уже разрушен, а новый образ ещё сознан не полностью. В таких случаях ядро убивает процесс сигналом SIGKILL.
Интерпретируемые сценарии
Ядро накладывает ограничение на максимальную длину текста после символов «#!» в начале сценария; символы за пределами границ игнорируются. До Linux 5.1 было ограничение в 127 символов. Начиная с Linux 5.1 ограничение установлено в 255 символов.
Семантика необязательного параметра интерпретатора сценариев различна в разных реализациях. В Linux, вся строка после имени интерпретатора передаётся интерпретатору как единый параметр, и эта строка может содержать пробельные символы. Однако, такое поведение отличается от других систем. Некоторые системы используют первый пробел в качестве признака окончания необязательного параметра. В других системах, интерпретатор сценариев может иметь несколько параметров, и пробелы в необязательном параметре используются для их разграничения.
На файлах со сценариями в Linux (как и большинстве других современных системах UNIX) игнорируются биты set-user-ID и set-group-ID.
execve() и EAGAIN
Это более подробное объяснение ошибки EAGAIN, которая возвращается (начиная с Linux 3.1) при вызове execve().
Ошибка EAGAIN может возникать, когда предшествующий вызов setuid(2), setreuid(2) или setresuid(2) приводит к изменению у процесса реального идентификатора пользователя и это изменение приводит к тому, что процесс превышает свой ограничитель ресурса RLIMIT_NPROC (т. е., количество процессов, принадлежащих новому реальному UID, превышает ограничитель ресурса). В версиях Linux с 2.6.0 по 3.0, это приводит к ошибке вызова set*uid() (до версии 2.6 ограничитель ресурса не учитывался для процессов, которые изменили идентификатор пользователя).
Начиная с Linux 3.1, описанный сценарий больше не приводит к ошибке в вызове set*uid(), так как это слишком часто приводило к дырам в безопасности, когда некорректное приложение не проверяет возвращаемое состояние и предполагает, что если вызывающий имеет права root, то вызов всегда выполняется успешно. Вместо этого вызов set*uid() теперь успешно изменяет реальный UID, но ядро устанавливает внутренний флаг с именем PF_NPROC_EXCEEDED, который означает, что был превышен ограничитель ресурса RLIMIT_NPROC. Если флаг PF_NPROC_EXCEEDED установлен и ограничитель ресурса всё ещё превышен на момент последующего вызова execve(), то вызов завершается с ошибкой EAGAIN. Такая логика ядра гарантирует, что ограничитель ресурса RLIMIT_NPROC будет учтён при обычной последовательности действий для привилегированных служб, а именно — fork(2) + set*uid() + execve().
Если ограничитель ресурса был не превышен на момент вызова execve() (так как другие процессы, принадлежащие этому реальному UID завершили работу между вызовом set*uid() и execve()), то вызов execve() выполнится успешно и ядро очистит флаг PF_NPROC_EXCEEDED у процесса. Флаг также очищается, если при успешном выполнении процессом последующего вызова fork(2).
Историческая справка
В UNIX V6 список аргументов вызова exec() заканчивался 0, а список аргументов main заканчивался -1. Поэтому, этот список аргументов не мог быть использован напрямую в последующем вызове exec(). Начиная с UNIX V7 оба списка стали оканчиваться NULL.
ПРИМЕР
Данная программа запускается второй программой, представленной ниже. Она просто выводит свои параметры командной строки по одному на строку.
/* myecho.c */
#include <stdio.h> #include <stdlib.h>
int main(int argc, char *argv[]) {
int j;
int j;
for (j = 0; j < argc; j++)
printf("argv[%d]: %s\n", j, argv[j]);
printf("argv[%d]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS); }
Эта программа может использоваться для запуска программы, чьё имя указано в параметре командной строки.
/* execve.c */
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
int main(int argc, char *argv[]) {
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Использование: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
fprintf(stderr, "Использование: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() возвращается только при ошибке */
exit(EXIT_FAILURE); }
perror("execve"); /* execve() возвращается только при ошибке */
exit(EXIT_FAILURE); }
Мы можем использовать вторую программу для запуска первой:
$ cc myecho.c -o myecho $ cc execve.c -o execve $ ./execve ./myecho argv[0]: ./myecho argv[1]: hello argv[2]: world
Также мы можем использовать эти программы для демонстрации использования интерпретатора сценариев. Для этого создадим сценарий, чей "интерпретатор" указывает на нашу программу myecho:
$ cat > script #!./myecho script-arg ^D $ chmod +x script
Теперь мы можем использовать нашу программу для запуска сценария:
$ ./execve ./script argv[0]: ./myecho argv[1]: script-arg argv[2]: ./script argv[3]: hello argv[4]: world
СМОТРИТЕ ТАКЖЕ
REFERENCED BY
cap_get_file(3), getexeccon(3), getfscreatecon(3), getkeycreatecon(3), getsockcreatecon(3), _exit(2), access(2), alarm(2), brk(2), capget(2), chdir(2), chmod(2), chroot(2), clone(2), close(2), eventfd(2), execveat(2), fcntl(2), flock(2), fork(2), get_robust_list(2), getgroups(2), getitimer(2), getpriority(2), getrlimit(2), getrusage(2), ioctl(2), ioctl_console(2), ioperm(2), iopl(2), keyctl(2), memfd_create(2), mlock(2), mount(2), open(2), perf_event_open(2), prctl(2), ptrace(2), sched_setaffinity(2), seccomp(2), semop(2), set_mempolicy(2), setpgid(2), setresuid(2), setreuid(2), setsid(2), setuid(2), shmop(2), sigaction(2), sigaltstack(2), signalfd(2), sigpending(2), sigprocmask(2), timer_create(2), timerfd_create(2), umask(2), vfork(2), catopen(3), exec(3), exit(3), fexecve(3), mq_close(3), posix_spawn(3), pthread_kill_other_threads_np(3), pthread_mutexattr_setrobust(3), sem_close(3), sigvec(3), system(3), console_ioctl(4), core(5), elf(5), proc(5), capabilities(7), cgroups(7), credentials(7), environ(7), inotify(7), pthreads(7), sched(7), signal(7), user_namespaces(7), vdso(7), inode(7), setpriv(1), clsync(1), iv_signal(3), stat(2), environ(5), pmcd(1), msocket(2viewos), sigstack(3), vlimit(3), vtimes(3), pthread_atfork(3), persistent-keyring(7), process-keyring(7), session-keyring(7), thread-keyring(7), user-keyring(7), user-session-keyring(7), xs(1), madvise(2), execve(2), strace(1), bosh(1), rt_sigsuspend(2), spawnveg(3ast), fanotify_mark(2)