ОБЗОР
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
ОПИСАНИЕ
execve() выполняет программу, задаваемую аргументом filename. В filename должно быть указано имя двоичного исполняемого файла или сценарий, начинающийся со строки вида:
#! интерпретатор [необязательные параметры]
Подробней о сценариях написано далее в "Интерпретируемые сценарии".
argv — это массив строковых параметров, передаваемых новой программе. По соглашению, в первой строке должно содержаться имя файла, относящееся к запускаемой программе. envp — это массив строк в формате ключ=значение, которые передаются новой программе в качестве окружения (environment). Оба массива argv и envp завершаются указателем null. К массиву параметров и окружению можно обратиться из вызываемой программой функции main, если она определена как:
int main(int argc, char *argv[], char *envp[])
При успешном выполнении execve() управление не возвращается, а код, данные, bss и стек вызвавшего процесса перезаписываются загруженной программой.
Если текущая программа выполнялась под управлением ptrace, то после успешного вызова execve() ей посылается сигнал SIGTRAP.
Если у файла программы, указанного в filename, установлен бит set-user-ID и файловая система, в которой он хранится, не смонтирована с параметром nosuid (флаг MS_NOSUID у mount(2)), и вызывающий процесс не выполняется под управлением ptrace, то фактический идентификатор пользователя вызывающего процесса меняется на идентификатор владельца файла программы. Точно также, если на файле программы установлен бит set-group-ID, то фактический идентификатор группы вызывающего процесса становится равным группе, которой принадлежит файл программы.
Фактический идентификатор пользователя процесса копируется в сохранённый идентификатор пользователя (set-user-ID), также фактический идентификатор группы копируется в сохранённый идентификатор группы (set-group-ID). Это копирование выполняется после изменения любого фактического идентификатора, которое происходит из-за выставленных бит режима set-user-ID и set-group-ID.
Если исполняемый файл является динамически-скомпонованным файлом в формате a.out, содержащим заглушки для динамических библиотек, то в начале выполнения этого файла вызывается динамический компоновщик Linux ld.so(8), который начинает выполнение с загрузки общих объектов в память и компонует их с исполняемым файлом.
Если исполняемый файл является динамически компонуемым файлом в формате ELF, то для загрузки необходимых общих объектов используется интерпретатор, указанный в сегменте PT_INTERP. Для программ, скомпонованных с glibc, обычно это /lib/ld-linux.so.2.
При вызове 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)).
В POSIX.1 определён список сохраняемых свойств процесса. Следующие свойства процесса, имеющиеся только в Linux, также не сохраняются при execve():
- *
- Устанавливается флаг 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().
Интерпретируемые сценарии
Интерпретируемый сценарий --- это текстовый файл, у которого установлен бит выполнения и первая строка имеет вид:
#! интерпретатор [необязательные параметры]
В поле интерпретатор должно быть указано имя файла запуска, это не имя самого файла сценария. Если в аргументе filename для execve() указан интерпретируемый сценарий, то интерпретатор будет вызван со следующими параметрами:
интерпретатор [необязательный параметр] имя файла параметр...
где параметр... — последовательность слов, указываемых аргументом argv в execve() начиная с argv[1].
В целях переносимости, необязательный параметр должен быть или пустым, или задаваться одним словом (т.е., не должен содержать пробельных символов); см. ЗАМЕЧАНИЯ далее.
Ограничения на размер параметров и окружения
Большинство реализаций 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 позволяет новой программе всегда иметь некоторое пространство под стек.) Начиная с 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
- В одном из каталогов префикса filename или интерпретатора не разрешён поиск. (см. также path_resolution(7))
- EACCES
- Файл или интерпретатор не являются обычным файлом.
- EACCES
- Не установлен бит выполнения на файле или сценарии или интерпретаторе ELF.
- EACCES
- Файловая система смонтирована с noexec.
- EAGAIN (начиная с Linux 3.1)
- Из-за изменения реального UID одним из вызовов set*uid() ранее, вызывающий всё ещё превышает ограничитель ресурса RLIMIT_NPROC (смотрите setrlimit(2)). Подробное объяснение этой ошибки смотрите в ЗАМЕЧАНИЯХ.
- EFAULT
- Значение filename или один из указателей в векторах argv или envp указывает за пределы доступного адресного пространства.
- EINVAL
- Исполняемый ELF-файл содержит более одного сегмента PT_INTERP (т.е., в нём указано более одного интерпретатора).
- EIO
- Произошла ошибка ввода-вывода.
- EISDIR
- Интерпретатор ELF является каталогом.
- ELIBBAD
- Не распознан формат интерпретатора ELF.
- ELOOP
- Во время определения filename, имени сценария или интерпретатора ELF встретилось слишком много символьных ссылок.
- EMFILE
- Было достигнуто ограничение по количеству открытых файловых дескрипторов на процесс.
- ENAMETOOLONG
- Слишком длинное значение аргумента filename.
- ENFILE
- Достигнуто максимальное количество открытых файлов в системе.
- ENOENT
- Файл filename, сценарий или интерпретатор ELF не существует, или не найдена динамическая библиотека, необходимая для файлового интерпретатора.
- ENOEXEC
- Не распознан формат исполняемого файла, он не подходит для архитектуры, или имеет ошибки в формате, из-за чего не может быть выполнен.
- ENOMEM
- Недостаточное количество памяти ядра.
- ENOTDIR
- Компонент пути в filename, сценарии или интерпретаторе ELF в действительности не является каталогом.
- EPERM
- Файловая система смонтирована с nosuid, пользователь не является суперпользователем, а на файле установлен бит set-user-ID или set-group-ID.
- EPERM
- Над процессом выполняется трассировка, пользователь не имеет прав суперпользователя, а у файла установлен бит set-user-ID или set-group-ID.
- ETXTBSY
- Заданный исполняемый файл был открыт на запись одним или более процессов.
СООТВЕТСТВИЕ СТАНДАРТАМ
POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD. В POSIX не описано поведение #!, но это существует (в нескольких вариантах) в других системах UNIX.ЗАМЕЧАНИЯ
Над процессами с установленными 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.
Интерпретируемые сценарии
Максимальная длина первой строки с указанным интерпретатором сценариев — 127 символов.Семантика необязательного параметра интерпретатора сценариев различна в разных реализациях. В Linux, вся строка после имени интерпретатора передаётся интерпретатору как единый параметр, и эта строка может содержать пробельные символы. Однако, такое поведение отличается от других систем. Некоторые системы используют первый пробел в качестве признака окончания необязательного параметра. В других системах, интерпретатор сценариев может иметь несколько параметров, и пробелы в необязательном параметре используются для их разграничения.
В Linux игнорируются биты 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; for (j = 0; j < argc; 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 }; if (argc != 2) { 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); }
Мы можем использовать вторую программу для запуска первой:
$ 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