(fwd) Re: права на файл

Andrey Gerzhov (kittle@freeland.kiev.ua)
Sat, 9 Oct 1999 21:28:36 +0300 (EEST)

-- forwarded message --
Path: freeland.kiev.ua!routki.ki.yurteh.net!carrier.kiev.ua!srcc!Gamma.RU!ddt.demos.su!not-for-mail
From: tolik@mpeks.tomsk.su (Anatoly A. Orehovsky)
Newsgroups: fido7.ru.unix
Subject: Re: права на файл
Date: 8 Oct 1999 09:48:45 +0400
Organization: CISA Ltd. InterNetNews site
Lines: 220
Sender: fido7@ddt.demos.su
Approved: <gateway@fido7.ru>
Distribution: fido7
Message-ID: <7tk0eh$20i@mpeks.tomsk.su>
References: <2956832281@mpeks.tomsk.su> <939300369@p222.f968.n5020.z2> <87aepvmazj.fsf@oskin.ncc.macomnet.ru>
NNTP-Posting-Host: ddt.demos.su
X-Trace: ddt.demos.su 939361727 23864 194.87.13.37 (8 Oct 1999 05:48:47 GMT)
X-Complaints-To: gatekeeper@fido7.ru
NNTP-Posting-Date: 8 Oct 1999 05:48:47 GMT
X-BeforeModerator-Path: mpeks.tomsk.su!not-for-mail
X-Newsreader: TIN [version 1.2 PL2]
Xref: freeland.kiev.ua fido7.ru.unix:11810

Serg Oskin (Serg.Oskin@f20.n5020.z2.fidonet.org) wrote:
: >>>>> "Slav" == Slav Matveev writes:

: Slav> Изначально я пытался понять что будет если у скрипта нет
: Slav> заголовка
: Slav> #! , мне почему-то всегда казалось что shell этот скрипт будет
: Slav> разбирать сам. Меня же попытались переубедить что будет сказано
: Slav> command not found, причем это аргументировалось всем чем только
: Slav> можно (в смысле очень умными словами). В том числе ссылкой на
: Slav> execv. Я привел кусок man'а по этой самой функции, в который ясно
: Slav> написано, что в случае ненахождения magic'a будет запущен
: Slav> shell. Мне только осталось не совсем понятно, какой конкретно
: Slav> шел. Тот из которого это команда выполнялась, тот, который для
: Slav> пользователя установлен, /usr/sh или что-то еще.

: Hесколько грубый пример, но всетаки:

: $ cat >tst
: if [ -f /etc/passwd ] ; then
: echo "Hello, world!"
: fi
: ^D
: $ file tst
: tst: Composer 669 Module sound data
: $

То есть, в данном случае Вы намекаете на то, что шеллом должна быть
некоторая программа, которая умеет Module sound data ? И что ядро будет
запускать эту программу ?

Гмм...

Давайте я еще раз попробую объяснить механизм запуска программы с помощью
exec ?

Только прошу, Slav Matveev и Valentin Nechayev, не подумайте чего плохого. У
меня и в мыслях нет делать "пальцы веером" и прочее. Ну зачем мне это ?

Просто есть некоторые заблуждения в вопросе exec, которые я пытаюсь
развеять. На пользу всем, а не для показухи - "какой я умный". Какая мне, в
конце концов разница, что про меня подумает человек, которого я никогда в
жизни не видел, скорее всего не увижу, и который никоим образом не может
повлиять на мою жизнь ? Равно как и я на его.

В общем, "не корысти для...", а в интересах эхи, темой которой, как ни
странно, является unix, а не мои амбиции.

Итак:

Выполнение функций и системных вызовов семейства exec. Текущее
состояние вопроса в BSD.

1. Системный вызов execve(2).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

В BSD единственным способом запустить программу из именованного файла
является системный вызов execve(2).

Его синтакис:

int execve(const char *path, const char *argv[], const char *envp[])

Где path - абсолютный (от корня) или относительный (от текущего рабочего
каталога) путь к файлу, который следует исполнить. Путь может состоять и из
одного только имени файла. Тогда файл будет разыскиваться в текущем рабочем
каталоге.

Ядро, исполняя системный вызов execve(2), делает (схематично и несколько
сокращенно):

1. Проверяет на правильность параметры path, argv, envp.
2. Проверяет путь к файлу, на который указывает path.
3. Проверяет - можно ли запускать файл ? в соответствии с типом файла
(файл должен быть обычный, а не каталог, device или fifo) и правами доступа.
4. Создает или находит уже существующий vnode для файла.
5. Делает отображение в память первой страницы файла.
6. Изучает заголовок файла с помощью так называемого exec switch - массива
зарегистрированных в системе подготовщиков к запуску определенных типов
файлов. ВНИМАНИЕ: если ни один из зарегистрированных обработчиков не
обнаруживает, что файл можно запустить, ядро возвращается из execve(2) с
выдачей ошибки ENOEXEC !
7. Если же один из подготовщиков обнаружил, что файл можно исполнить, он
подготавливает необходимые данные (например, правильно размещает файл в
памяти) и ядро продолжает выполнение execve(2).
8. Если подготовщик сообщает ядру, что файл следует интерпретировать, то 2-6
повторяется уже для интерпретатора. То есть, реально запустится или не
запустится интерпретатор.
9. Вот теперь уже для подготовленного к запуску файла или интерпретатора
делаются suid/sgid изменения прав доступа. Из этого следует, что биты
suid/sgid, поставленные на интерпретируемый скрипт, для ядра не имеют
никакого значения.
10. Производится еще некоторая работа и execve(2) завершается хитрым
образом, возвращаясь на точку входа новой программы.

2. Что делает подготовщик к запуску, вызываемый из exec switch ?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Он проверяет передаваемую ему первую страницу образа в памяти
исполнимого файла на предмет распознования СВОЕГО типа файла.
Первичное распознование происходит обычно по некоторым MAGIC-признакам.
Например, подготовщик может проверить первые несколько байтов файла
на совпадение с некоторым значением - числом или строкой. Если эта
первичная проверка положительна, могут производиться дополнительные
проверки на, так сказать, вторичные признаки.

Работа, производимая ядром, не имеет никакого отношения к утилите file(1).

Если подготовщик не смог распознать файл (частный случай - MAGIC верен,
вторичные признаки не прошли), то в execve(2) возвращается ENOEXEC !

2. Если все проверки прошли, подготовщик пытается произвести собственно
работу по подготовке файла к запуску. На этом этапе тоже могут быть ошибки,
но они не будут ENOEXEC.

Иными словами, подготовщик может вернуть ENOEXEC только в том случае, если
он не может определить тип файла как свой. А execve(2) возвращает ENOEXEC
только в том случае, если все подготовщики из exec switch вернули ENOEXEC.
То есть, если exec switch вообще не смог определить тип файла. Тут есть
ТОЛЬКО две ситуации - тип файла определен и ядро пытается запустить его (в
этом случае НИКОГДА не будет ENOEXEC) или тип файла как-бы НЕ определен
вообще никак (он может быть определен, но, по некоторым причинам подготовщик
отказывается его выполнить наотрез, даже не пытаясь производить подготовку)
и ядро его не будет запускать (в этом, и только в этом случае execve(2)
вернет ENOEXEC).

3. Один из подготовщиков называется exec_shell. Он распознает тип файла по
MAGIC - двум первым байтам файла, которые ДОЛЖНЫ быть равны '#!' (число
0x2123 или 0x2321 в зависимости от byte ordering системы).

ЕСЛИ ПЕРВЫЕ ДВА БАЙТА ФАЙЛА НЕ РАВНЫ '#!', ЯДРО НЕ РАСПОЗНАЕТ ФАЙЛ КАК
ИНТЕРПРЕТИРУЕМЫЙ СКРИПТ. Стало быть, САМО ЯДРО НИКОГДА НЕ БУДЕТ ВЫПОЛНЯТЬ
СКРИПТ БЕЗ '#!'.

Если же проверка на MAGIC прошла, подготовщик делает дополнительные проверки
на предмет, не пытаются ли запустить в качестве shell скрипт (запретная
ситуация, ENOEXEC(!)) и правильна ли строка запуска shellа (тоже ENOEXEC).
После чего подправляются параметры запуска файла на предмет того, что для
execve(2) делается пометка, что файл является скриптом и делается так, чтобы
прочие подготовщики файлов к исполнению при желании смогли бы получить
параметры, передаваемые в командной строке скрипту, плюс имя файла со
скриптом, плюс параметры интерпретатора из строки '#!'.

Особо попрошу заметить, что, если в строке '#!' shell указан без пути и не
находится в текущем каталоге, то execve(2) завершится с ENOENT.

3. Знает ли ядро в контексте exec о /bin/sh ?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Нет, не знает. Если в скрипте в строке '#!' не указан этот shell, то /bin/sh
автоматом вызываться из ядра НЕ БУДЕТ.

4. Библиотечные libc функции семейства exec.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Перечень:

execl(3), execlp(3), execle(3), exect(3), execv(3), execvp(3)

Все они являются более или менее продвинутыми надстройками над execve(2).
Реальный запуск программы производится ТОЛЬКО execve(2).

execl(3), execle(3), exect(3) и execv(3) различаются только аргументами.
Внутренняя работа функций сводится к простому преобразованию своих
аргументов в аргументы execve(2). При этом, параметр path в этих функциях
идентичен параметру path в execve(2).

execlp(3) и execvp(3) более "интеллектуальны". Они имеют некоторый
shell-подобный сервис для поиска и запуска программ. А именно:

1. Вместо параметра path в этих функциях используется параметр file. Если
это абсолютный или относительный путь к исполняемому файлу, функция
оставляет его без изменений при передаче execve(2). Если же file - это
просто имя файла без указания пути, то execlp(3) и execvp(3) производят
поиск файла в соответствии с путями, указанными в переменной окружения PATH.
При этом, путь последовательно конструируется из очередного элемента PATH и
параметра file, вызывается execve(2) и, в случае возврата от execve(2)
ENOENT, процедура повторяется до полного перебора всех путей в PATH.

Таким образом, execlp(3) и execlp(3) имеют некоторое преимущество в поиске
исполняемого файла, поскольку могут искать файл в нескольких каталогах.
Однако, есть и недостатки: если в PATH не указать явно текущий каталог, в
нем поиск не будет производиться, в отличие от прочих функций.

2. Кроме того, execlp(3) и execvp(3) делают такое допущение:

Если execve(2) вернул ENOEXEC, эти функции считают, что исполнимый файл
является скриптом /bin/sh без строки '#!' и пытаются запустить /bin/sh с
передачей ему построенного с помощью PATH пути на исполнение.

Таким образом, о /bin/sh в контексте execlp(3) и execvp(3), и только их из
всего набора функций семейства exec, знает libc.

5. Где все это можно посмотреть ?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

execve(2) :
man execve, /usr/src/sys/kern/kern_exec.c, /usr/src/sys/kern/imgact*.c

execl(3), execlp(3), execle(3), exect(3), execv(3), execvp(3):

man exec, /usr/src/lib/libc/gen/exec.c

6. Замечания.
^^^^^^^^^^^^^

Все вышеозначенное абсолютно точно относится к BSD-линии *nix. С большОй
вероятностью и к прочим *nix. Однако, в случае прочих *nix следует
ориентироваться на POSIX.

В принципе, стандарт POSIX не запрещает реализовывать функции семейства
exec в ядре. В таком случае, некоторые из POSIX-совместимых систем, в том
числе и некоторые *nix, могут иметь ядро, знающее в случае execlp и execvp
о /bin/sh.

Из области догадок: скорее всего таких *nix нет. И execlp, execvp
реализованы через libc. Особенно вероятно это для Linux, как
использующего gnulibc.

-- 
Anatoly A. Orehovsky. AO9-RIPE. AAO1-RIPN
http://www.tekmetrics.com/transcript.shtml?pid=6064
-- end of forwarded message --

-- 
С тем, что не помешает никогда,
                                               Kittle