Системное программирование
Лекция 10:
Пользователи и группы. Вход в систему. Real and effective user. Права доступа у процессов, файлов. Сессии. Демонизация процесса.
The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.
Дедлайны:
Процессы
Аппаратура
Время
Файловая
система
IPC
Сеть
Пользователи
Структуры
данных
Виртуализация
$> cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
vladislav:x:1000:1000:Vladislav,,,:/home/vladislav:/bin/bash
$> cat /etc/shadow
root:!:17704:0:99999:7:::
daemon:*:17647:0:99999:7:::
bin:*:17647:0:99999:7:::
sys:*:17647:0:99999:7:::
vladislav:$6$mVx1o/.g$vE2srpP.IWDwvUh4ozSOmmgrVaIlvi/8hCu2InOmV2G1:17704:0:99999:7:::
login : x : id : group_id : comment : home_dir : start_bin
Файл пользователей
Файл хешей паролей
login : hash : update_date : min_to_change : max_valid : warn : inactive : expire
root$> useradd -m -c "comment" sysproguser
root$> tail -n 1 /etc/passwd
sysproguser:x:1001:1002:comment:/home/sysproguser:/bin/sh
root$> tail -n 1 /etc/shadow
sysproguser:!:17861:0:99999:7:::
Создание пользователя
root$> passwd sysproguser
root$> Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root$> su vladislav
vladislav$> su sysproguser
Password:
sysproguser$>
^D
vladislav$>
Смена пароля
root$> userdel -r sysproguser
Удаление пользователя
Kernel-space
User-space
/**
* 30.09.2018
* include/linux/cred.h
* 39 lines.
*/
struct cred {
kuid_t uid;
kgid_t gid;
kuid_t suid;
kgid_t sgid;
kuid_t euid;
kgid_t egid;
struct group_info *group_info;
};
/etc/passwd
/etc/shadow
/etc/group
...
useradd
userdel
usermod
...
В ядре нет пользователей. Только их id
Имена, логины, пароли - все в user-space
struct task_struct {
/* ... */
const struct cred *real_cred;
const struct cred *cred;
/* ... */
};
Следствие user-space хранения пользователей
$> touch test.txt
$> ls -l
total 0
-rw-rw-r-- 1 vladislav vladislav 0 ноя 26 18:01 test.txt
$> chown 2000 test.txt
$> ls -l
total 0
-rw-rw-r-- 1 2000 vladislav 0 ноя 26 18:01 test.txt
Использование несуществующих id
$> cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
sudo:x:27:vladislav
bluetooth:x:113:
ssh:x:115:
vladislav:x:1000:
name : x : group_id : members
$> cat /etc/gshadow
root:*::
daemon:*::
bin:*::
sys:*::
sudo:*::vladislav
bluetooth:!::
ssh:!::
vladislav:!::
name : hash : admins : members
root$> groupadd testgroup
root$> tail -n 1 /etc/group
testgroup:x:1002:
root$> useradd -m sysproguser
root$> useradd -m sysproguser2
root$> usermod -aG testgroup sysproguser
root$> usermod -aG testgroup sysproguser2
root$> tail -n 3 /etc/group
testgroup:x:1002:sysproguser,sysproguser2
sysproguser:x:1003:
sysproguser2:x:1004:
root$> groupdel testgroup
root$> tail -n 3 /etc/group
vboxsf:x:999:
sysproguser:x:1003:
sysproguser2:x:1004:
root$> userdel -r sysproguser
root$> userdel -r sysproguser2
Добавление пользователя в группу: a - append, G - groups.
Теперь в группе два участника
Зачистка
int
main()
{
uid_t uid, euid, suid;
getresuid(&uid, &euid, &suid);
printf("uid = %d, euid = %d, "\
"suid = %d\n", (int) uid, "\
"(int) euid, (int) suid);
return 0;
}
$> gcc 4_setuid.c
$> ls -l
-rwxr-xr-x 1 vladislav vladislav a.out
$> sudo chown root ./a.out
$> ls -l
-rwxr-xr-x 1 root vladislav a.out
$> ./a.out
uid = 1000, euid = 1000, suid = 1000
$> sudo chmod u+s ./a.out
$> ls -l
-rwsr-xr-x 1 root vladislav a.out
$> ./a.out
uid = 1000, euid = 0, suid = 0
Set-user-ID и Set-group-ID биты. Исполняемый файл с такими битами меняет EUID процесса на UID владельца
Linux-specific функция, которой можно распечатать три UID-а процесса
Собрал исполняемый файл. Владелец и группа - Я
Поменял владельца на рута
Запустил - пока не помогло. Нет бита set-user-ID
Добавил бит set-user-ID
Реальный UID остался 1000, но эффективный - 0 (root)
uid_t uid, euid, suid;
void show_uids()
{
getresuid(&uid, &euid, &suid);
printf("uid = %d, euid = %d, suid = %d\n", (int) uid, (int) euid, (int) suid);
}
int main(int argc, const char **argv)
{
uid_t new_uid1, new_uid2, new_uid3;
new_uid1 = atoi(argv[1]), new_uid2 = atoi(argv[2]), new_uid3 = atoi(argv[3]);
show_uids();
printf("Set EUID to %d\n", (int) new_uid1);
if (seteuid(new_uid1) == -1)
printf("error = %s\n", strerror(errno));
show_uids();
printf("Set EUID to %d\n", (int) new_uid2);
if (seteuid(new_uid2) == -1)
printf("error = %s\n", strerror(errno));
show_uids();
printf("Set UID to %d\n", (int) new_uid3);
if (setuid(new_uid3) == -1)
printf("error = %s\n", strerror(errno));
show_uids();
return 0;
}
Распечатывалка всех uid
Программа принимает три UID-а и по очереди их ставит
Первые две попытки по-разному меняют EUID
Последняя пытается поменять и настоящий
$> gcc 5_seteuid.c
$> ./a.out 1000 0 1000
uid = 1000, euid = 1000, suid = 1000
Set EUID to 1000
uid = 1000, euid = 1000, suid = 1000
Set EUID to 0
error = Operation not permitted
uid = 1000, euid = 1000, suid = 1000
Set UID to 1000
uid = 1000, euid = 1000, suid = 1000
Все UID были мои. Так что seteuid() в самого себя сработал
А получить root-а не смог
$> sudo ./a.out 1000 0 500
uid = 0, euid = 0, suid = 0
Set EUID to 1000
uid = 0, euid = 1000, suid = 0
Set EUID to 0
uid = 0, euid = 0, suid = 0
Set UID to 500
uid = 500, euid = 500, suid = 500
Попробую запустить рутом
seteuid() поменял только EUID
setuid() поменял все. Обратно в рута уже не вернуться
Зачем suid?
$> sudo chown root ./a.out
$> sudo chmod u+s ./a.out
$> ls -l
-rwsr-xr-x 1 root vladislav a.out
$> ./a.out 1000 0 1000
uid = 1000, euid = 0, suid = 0
Set EUID to 1000
uid = 1000, euid = 1000, suid = 0
Set EUID to 0
uid = 1000, euid = 0, suid = 0
Set UID to 1000
uid = 1000, euid = 1000, suid = 1000
Пусть UID изначально не будет рутом, а EUID - будет. Это делается через set-user-ID бит
В SUID сохраняется предыдущее значение EUID, из которого потом можно его восстановить
void
seteuid(uid_t new_euid)
{
if (euid != 0 && new_euid != euid &&
new_euid != suid && new_euid != uid)
error();
if (new_euid != euid) {
suid = euid;
euid = new_euid;
}
}
void
setuid(uid_t new_uid)
{
if (euid == 0)
uid = new_uid;
seteuid(new_uid);
}
Все тоже самое работает и для GID - Group ID. GID тоже есть Saved - SGID.
owner group rwx rwx rwx
Владелец
Группа
Права на доступ для владельца, группы, остальных
r | w | x | |
---|---|---|---|
Файл | Чтение | Запись | Выполнение |
Директория | Получение списка файлов | Удаление/добавление/переименование файлов | Поиск файла по имени |
set-user-ID | set-group-ID | sticky | |
---|---|---|---|
Исполняемый файл | Установить EUID в UID владельца | Установить EGID в GID владельца | Раньше закреплял загруженную секцию .text в ОП. Сейчас ничего. |
Обычный файл | - | - | - |
Директория | - | Все новое содержимое будет иметь группу, как у этой директории | Удалять и переименовывать можно только свои файлы |
$> ls -l
total 36
drwxrwxr-x 2 vladislav vladislav 4096 ноя 27 04:46 .
drwxr-xr-x 16 vladislav vladislav 4096 ноя 27 04:15 ..
-rwxr-x--- 1 vladislav vladislav 230 ноя 27 03:08 4_setuid.c
-rwxr-x--- 1 vladislav vladislav 848 ноя 27 04:40 5_seteuid.c
-rwxr-x--- 1 vladislav vladislav 660 ноя 27 04:45 6_seteuid_twice.c
-rwsr-xr-x 1 root vladislav 8664 ноя 27 04:46 a.out
-rw-rw-r-- 1 vladislav vladislav 16 ноя 27 02:35 test.c
-rw-rw-r-- 1 2000 vladislav 0 ноя 26 18:01 test.txt
$> mkfifo myfifo
$> ls -l myfifo
prw-r--r-- 1 vladislav vladislav 0 ноя 27 05:56 myfifo
$> nc -l -U ./socket.s
^C
$> ls -l socket.s
srwxr-xr-x 1 vladislav vladislav 0 ноя 27 06:00 socket.s
Маска прав: три по три бита
Тип файла
Владелец
Группа
$> mkdir testdir
$> ls -l testdir
drwxr-xr-x 2 vladislav vladislav 4096 ноя 27 06:06 testdir
$> chmod g+s testdir
$> ls -l testdir
drwxr-sr-x 2 vladislav vladislav 4096 ноя 27 06:06 testdir
$> chmod o+t testdir
$> ls -l testdir
drwxr-sr-t 2 vladislav vladislav 4096 ноя 27 06:06 testdir
$> ls -ld /tmp
drwxrwxrwt 13 root root 4096 ноя 27 06:09 /tmp
Установка set-group-ID бита
Установка sticky бита
Пример sticky директории - /tmp
S_IRWXU 00700 user rwx
S_IRUSR 00400 user r--
S_IWUSR 00200 user -w-
S_IXUSR 00100 user --x
S_IRWXG 00070 group rwx
S_IRGRP 00040 group r--
S_IWGRP 00020 group -w-
S_IXGRP 00010 group --x
S_IRWXO 00007 others rwx
S_IROTH 00004 others r--
S_IWOTH 00002 others -w-
S_IXOTH 00001 others --x
S_ISUID 0004000 set-user-ID bit
S_ISGID 0002000 set-group-ID bit
S_ISVTX 0001000 sticky bit
int
open(const char *pathname, int flags, mode_t mode);
sem_t *
sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
int
mkfifo(const char *pathname, mode_t mode);
S_IRUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXOTH = r-xrw---x
int
main()
{
int fd = open("tmp.txt",
O_CREAT | O_RDWR,
S_IRUSR | S_IXUSR);
if (fd == -1) {
printf("error = %s\n",
strerror(errno));
} else {
close(fd);
}
return 0;
}
$> gcc 6_access_mask.c
$> ./a.out
$> ls -l tmp.txt
-r-x------ vladislav vladislav tmp.txt
$> echo 123 > tmp.txt
-bash: tmp.txt: Permission denied
$> cat tmp.txt
$>
Права новых ресурсов вычитают маску umask. Umask - тоже 9 битов. Если в маске бит 1, то у всех новых ресурсов он будет 0
int
main()
{
mode_t mode = S_IRWXU | S_IRWXO |
S_IRWXG;
int fd = open("tmp.txt",
O_CREAT | O_RDWR,
mode);
if (fd == -1) {
printf("error = %s\n",
strerror(errno));
} else {
close(fd);
}
return 0;
}
$> umask
0022
$> gcc 7_umask.c
$> ./a.out
$> ls -l tmp.txt
-rwxr-xr-x vladislav vladislav tmp.txt
S_IRWXU | S_IRWXO | S_IRWXG = rwxrwxrwx
(S_IRWXU | S_IRWXO | S_IRWXG) & ~umask = rwxr-xr-x
Восьмеричная система
Процесс:
Ресурс:
Смена прав
int
chmod(const char *pathname, mode_t mode);
int
fchmod(int fd, mode_t mode);
Посмотреть права
int
stat(const char *pathname,
struct stat *statbuf);
int
fstat(int fd, struct stat *statbuf);
struct stat {
/* ... */
mode_t st_mode;
/* ... */
};
Посмотреть *ID процесса
uid_t getuid(void);
uid_t geteuid(void);
int setuid(uid_t uid);
int seteuid(uid_t euid);
gid_t getgid(void);
gid_t getegid(void);
int setgid(gid_t gid);
int setegid(gid_t egid);
Имя <-> ID
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
int
setpgid(pid_t pid, pid_t pgid);
pid_t
getpgid(pid_t pid);
pid_t
getpgrp(void);
int
setpgrp(void);
int
main(int argc, const char **argv)
{
pid_t id = getpgrp();
pid_t pid = getpid();
int i = 0;
if (argv[1][0] == 'w')
printf("1\n");
else
scanf("%d", &i);
fprintf(stderr, "group = %d, "\
"pid = %d\n", (int) id,
(int) pid);
return 0;
}
$> gcc 8_pgrp.c
$> ./a.out write | ./a.out read
group = 97406, pid = 97406
group = 97406, pid = 97407
proc1
proc2
proc3
proc4
proc5
shell
Лидер сессии
Фоновые группы процессов
Передовая группа процессов
proc6
proc7
Сигнал о потере терминала
Сигналы SIGINT, SIGQUIT. Терминальный ввод
pid_t
setsid(void);
Создается новая сессия с одной группой. Процесс становится лидером обоих.
Что сложного в создании демона?
int daemonize(const char *log_file)
{
if (fork() > 0)
exit(0);
int fd = open(log_file, O_CREAT |
O_WRONLY | O_TRUNC,
S_IRWXU);
dup2(fd, STDOUT_FILENO);
close(fd);
close(STDIN_FILENO);
close(STDERR_FILENO);
return setsid();
}
int main(int argc, const char **argv)
{
daemonize(argv[1]);
int server = socket(AF_INET, SOCK_STREAM,
IPPROTO_TCP);
/* ... */
Демонизируем сервер из предыдущих лекций
Fork() создаст ребенка, который гарантированно не будет лидером группы
Новый процесс будет отключен от терминала - стандартные потоки надо закрыть или заменить
setsid() создает новую сессию и группу, и сделает этот процесс их лидером, оставив терминал в старой сессии
Достаточно позвать демонизатор, и любой процесс форкнет от себя демона, а сам завершится
$> gcc 9_daemon.c
$> ./a.out log.txt
$> ps aux | grep a.out
v.shpilevoy 97920 0:00.00 grep a.out
v.shpilevoy 97914 0:00.00 ./a.out log.txt
$> ps -fp 97914
UID PID PPID C STIME TTY CMD
502 97914 1 0 11:31 ?? ./a.out log.txt
$> gcc 9_client.c
$> ./a.out
300
Sent 300
Received 301
^D
$> cat log.txt
New client
Interact with fd 2
Received 300
Sent 301
Interact with fd 2
Client disconnected
$> kill -9 97914