#define FOUND 1 /* ответ найден */
#define NOTFOUND 0 /* ответ не найден */
int value; /* результат */
main(){ int i;
for(i=2; i < 10; i++){
printf( "пробуем i=%d\n", i);
if( test1(i) == FOUND ){
printf("ответ %d\n", value); break;
}
}
}
test1(i){ int j;
for(j=1; j < 10 ; j++ ){
printf( "пробуем j=%d\n", j);
if( test2(i,j) == FOUND ) return FOUND;
/* "сквозной" return */
}
return NOTFOUND;
}
test2(i, j){
printf( "пробуем(%d,%d)\n", i, j);
if( i * j == 21 ){
printf( " Годятся (%d,%d)\n", i,j);
value = j; return FOUND;
}
return NOTFOUND;
}
Вот ответ, использующий нелокальный переход вместо цепочки return-ов:
#include
jmp_buf jmp;
main(){ int i;
if( i = setjmp(jmp)) /* после прыжка */
printf("Ответ %d\n", --i);
else /* установка точки */
for(i=2; i < 10; i++)
printf( "пробуем i=%d\n", i), test1(i);
}
test1(i){ int j;
for(j=1; j < 10 ; j++ )
printf( "пробуем j=%d\n", j), test2(i,j);
}
test2(i, j){
printf( "пробуем(%d,%d)\n", i, j);
if( i * j == 21 ){
printf( " Годятся (%d,%d)\n", i,j);
longjmp(jmp, j + 1);
}
}
Обратите внимание, что при возврате ответа через второй аргумент longjmp мы прибавили
1, а при печати ответа мы эту единицу отняли. Это сделано на случай ответа j==0,
чтобы функция setjmp не вернула бы в этом случае значение 0 (признак установки конт-
рольной точки).
6.7.2. В чем ошибка?
#include
А. Богатырев, 1992-95 - 235 - Си в UNIX
jmp_buf jmp;
main(){
g();
longjmp(jmp,1);
}
g(){ printf("Вызвана g\n");
f();
printf("Выхожу из g\n");
}
f(){
static n;
printf( "Вызвана f\n");
setjmp(jmp);
printf( "Выхожу из f %d-ый раз\n", ++n);
}
Ответ: longjmp делает прыжок в функцию f(), из которой уже произошел возврат управле-
ния. При переходе в тело функции в обход ее заголовка не выполняются машинные команды
"пролога" функции - функция остается "неактивированной". При возврате из вызванной
таким "нелегальным" путем функции возникает ошибка, и программа падает. Мораль: в
функцию, которая НИКЕМ НЕ ВЫЗВАНА, нельзя передавать управление. Обратный прыжок -
из f() в main() - был бы законен, поскольку функция main() является активной, когда
управление находится в теле функции f(). Т.е. можно "прыгать" из вызванной функции в
вызывающую: из f() в main() или в g(); и из g() в main();
-- --
| f | стек прыгать
| g | вызовов сверху вниз
| main | функций можно - это соответствует
---------- выкидыванию нескольких
верхних слоев стека
но нельзя наоборот: из main() в g() или f(); а также из g() в f(). Можно также
совершать прыжок в пределах одной и той же функции:
f(){ ...
A: setjmp(jmp);
...
longjmp(jmp, ...); ...
/* это как бы goto A; */
}
6.8. Хозяин файла, процесса, и проверка привелегий.
UNIX - многопользовательская система. Это значит, что одновременно на разных
терминалах, подключенных к машине, могут работать разные пользователи (а может и один
на нескольких терминалах). На каждом терминале работает свой интерпретатор команд,
являющийся потомком процесса /etc/init.
6.8.1. Теперь - про функции, позволяющие узнать некоторые данные про любого пользо-
вателя системы. Каждый пользователь в UNIX имеет уникальный номер: идентификатор
пользователя (user id), а также уникальное имя: регистрационное имя, которое он наби-
рает для входа в систему. Вся информация о пользователях хранится в файле
/etc/passwd. Существуют функции, позволяющие по номеру пользователя узнать регистра-
ционное имя и наоборот, а заодно получить еще некоторую информацию из passwd:
А. Богатырев, 1992-95 - 236 - Си в UNIX
#include
#include
struct passwd *p;
int uid; /* номер */
char *uname; /* рег. имя */
uid = getuid();
p = getpwuid( uid );
...
p = getpwnam( uname );
Эти функции возвращают указатели на статические структуры, скрытые внутри этих функ-
ций. Структуры эти имеют поля:
p->pw_uid идентиф. пользователя (int uid);
p->pw_gid идентиф. группы пользователя;
и ряд полей типа char[]
p->pw_name регистрационное имя пользователя (uname);
p->pw_dir полное имя домашнего каталога
(каталога, становящегося текущим при входе в систему);
p->pw_shell интерпретатор команд
(если "", то имеется в виду /bin/sh);
p->pw_comment произвольная учетная информация (не используется);
p->pw_gecos произвольная учетная информация (обычно ФИО);
p->pw_passwd зашифрованный пароль для входа в
систему. Истинный пароль нигде не хранится вовсе!
Функции возвращают значение p==NULL, если указанный пользователь не существует (нап-
ример, если задан неверный uid). uid хозяина данного процесса можно узнать вызовом
getuid, а uid владельца файла - из поля st_uid структуры, заполняемой системным вызо-
вом stat (а идентификатор группы владельца - из поля st_gid). Задание: модифицируйте
наш аналог программы ls, чтобы он выдавал в текстовом виде имя владельца каждого
файла в каталоге.
6.8.2. Владелец файла может изменить своему файлу идентификаторы владельца и группы
вызовом
chown(char *имяФайла, int uid, int gid);
т.е. "подарить" файл другому пользователю. Забрать чужой файл себе невозможно. При
этой операции биты S_ISUID и S_ISGID в кодах доступа к файлу (см. ниже) сбрасываются,
поэтому создать "Троянского коня" и, сделав его хозяином суперпользователя, получить
неограниченные привелегии - не удастся!
6.8.3. Каждый файл имеет своего владельца (поле di_uid в I-узле на диске или поле
i_uid в копии I-узла в памяти ядра[*]). Каждый процесс также имеет своего владельца
(поля u_uid и u_ruid в u-area). Как мы видим, процесс имеет два параметра, обознача-
ющие владельца. Поле ruid называется "реальным идентификатором" пользователя, а uid -
"эффективным идентификатором". При вызове exec() заменяется программа, выполняемая
данным процессом:
____________________
[*] При открытии файла и вообще при любой операции с файлом, в таблицах ядра заво-
дится копия I-узла (для ускорения доступа, чтобы постоянно не обращаться к диску).
Если I-узел в памяти будет изменен, то при закрытии файла (а также периодически через
некоторые промежутки времени) эта копия будет записана обратно на диск. Структура
I-узла в памяти - struct inode - описана в файле , а на диске - struct
dinode - в файле .
А. Богатырев, 1992-95 - 237 - Си в UNIX
старая программа exec новая программа
ruid -->----------------->---> ruid
uid -->--------*-------->---> uid (new)
|
выполняемый файл
i_uid (st_uid)
Как видно из этой схемы, реальный идентификатор хозяина процесса наследуется. Эффек-
тивный идентификатор обычно также наследуется, за исключением одного случая: если в
кодах доступа файла (i_mode) выставлен бит S_ISUID (set-uid bit), то значение поля
u_uid в новом процессе станет равно значению i_uid файла с программой:
/* ... во время exec ... */
p_suid = u_uid; /* спасти */
if( i_mode & S_ISUID ) u_uid = i_uid;
if( i_mode & S_ISGID ) u_gid = i_gid;
т.е. эффективным владельцем процесса станет владелец файла. Здесь gid - это иденти-
фикаторы группы владельца (которые тоже есть и у файла и у процесса, причем у про-
цесса - реальный и эффективный).
Зачем все это надо? Во-первых затем, что ПРАВА процесса на доступ к какому-либо
файлу проверяются именно для эффективного владельца процесса. Т.е. например, если
файл имеет коды доступа
mode = i_mode & 0777;
/* rwx rwx rwx */
и владельца i_uid, то процесс, пытающийся открыть этот файл, будет "проэкзаменован" в
таком порядке:
if( u_uid == 0 ) /* super user */
то доступ разрешен;
else if( u_uid == i_uid )
проверить коды (mode & 0700);
else if( u_gid == i_gid )
проверить коды (mode & 0070);
else проверить коды (mode & 0007);
Процесс может узнать свои параметры:
unsigned short uid = geteuid(); /* u_uid */
unsigned short ruid = getuid(); /* u_ruid */
unsigned short gid = getegid(); /* u_gid */
unsigned short rgid = getuid(); /* u_rgid */
а также установить их:
setuid(newuid); setgid(newgid);
Рассмотрим вызов setuid. Он работает так (u_uid - относится к процессу, издавшему
этот вызов):
if( u_uid == 0 /* superuser */ )
u_uid = u_ruid = p_suid = newuid;
else if( u_ruid == newuid || p_suid == newuid )
u_uid = newuid;
else неудача;
Поле p_suid позволяет set-uid-ной программе восстановить эффективного владельца,
который был у нее до exec-а.
А. Богатырев, 1992-95 - 238 - Си в UNIX
Во-вторых, все это надо для следующего случая: пусть у меня есть некоторый файл
BASE с хранящимися в нем секретными сведениями. Я являюсь владельцем этого файла и
устанавливаю ему коды доступа 0600 (чтение и запись разрешены только мне). Тем не
менее, я хочу дать другим пользователям возможность работать с этим файлом, однако
контролируя их деятельность. Для этого я пишу программу, которая выполняет некоторые
действия с файлом BASE, при этом проверяя законность этих действий, т.е. позволяя
делать не все что попало, а лишь то, что я в ней предусмотрел, и под жестким контро-
лем. Владельцем файла PROG, в котором хранится эта программа, также являюсь я, и я
задаю этому файлу коды доступа 0711 (rwx--x--x) - всем можно выполнять эту программу.
Все ли я сделал, чтобы позволить другим пользоваться базой BASE через программу (и
только нее) PROG? Нет!
Если кто-то другой запустит программу PROG, то эффективный идентификатор про-
цесса будет равен идентификатору этого другого пользователя, и программа не сможет
открыть мой файл BASE. Чтобы все работало, процесс, выполняющий программу PROG, дол-
жен работать как бы от моего имени. Для этого я должен вызовом chmod либо командой
chmod u+s PROG
добавить к кодам доступа файла PROG бит S_ISUID.
После этого, при запуске программы PROG, она будет получать эффективный иденти-
фикатор, равный моему идентификатору, и таким образом сможет открыть и работать с
файлом BASE. Вызов getuid позволяет выяснить, кто вызвал мою программу (и занести
это в протокол, если надо).
Программы такого типа - не редкость в UNIX, если владельцем программы (файла ее
содержащего) является суперпользователь. В таком случае программа, имеющая бит дос-
тупа S_ISUID работает от имени суперпользователя и может выполнять некоторые дейст-
вия, запрещенные обычным пользователям. При этом программа внутри себя делает всячес-
кие проверки и периодически спрашивает пароли, то есть при работе защищает систему от
дураков и преднамеренных вредителей. Простейшим примером служит команда ps, которая
считывает таблицу процессов из памяти ядра и распечатывает ее. Доступ к физической
памяти машины производится через файл-псевдоустройство /dev/mem, а к памяти ядра -
/dev/kmem. Чтение и запись в них позволены только суперпользователю, поэтому прог-