Лаборатория Tarantool

Системное программирование

Лекция 10:

Пользователи и группы. Вход в систему. Real and effective user. Права доступа у процессов, файлов. Сессии. Демонизация процесса.

The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.

Новости

Дедлайны:

  • планировщик корутин: 19 марта, штраф -10
  • shell: 2 апреля, штраф -10
  • файловая система: 16 апреля, штраф -10
  • пул потоков: 30 апреля

Ядро Linux

Процессы

Аппаратура

Время

Файловая

система

IPC

Сеть

Пользователи

Структуры

данных

Виртуализация

Пользователи и группы [1]

$> 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

Пользователи и группы [2]

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

Удаление пользователя

Пользователи и группы [3]

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;
        /* ... */
};

Пользователи и группы [4]

Следствие 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

Пользователи и группы [5]

$> 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

Пользователи и группы [6]

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.

Теперь в группе два участника

Зачистка

Real и effective пользователь [1]

  • Реальный - кто запустил процесс
  • Эффективный - от чьего имени работает процесс

Real и effective пользователь [2]

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)

Setuid() и seteuid() [1]

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

Последняя пытается поменять и настоящий

Setuid() и seteuid() [2]

$> 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, из которого потом можно его восстановить

Setuid() и seteuid() [3]

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.

Все ID процесса

  • UID, GID
  • EUID, EGID
  • SUID, SGID
  • Добавочные группы ...

Права доступа к ресурсам [1]

  • Ресурсы: файлы, устройства, UNIX сокеты, IPC
  • Правовые атрибуты: пользователь, группа, маска доступа 9 бит, set-user-ID, set-group-ID, sticky бит

Права доступа к ресурсам [2]

owner     group     rwx     rwx     rwx

Владелец

Группа

Права на доступ для владельца, группы, остальных

r w x
Файл Чтение Запись Выполнение
Директория Получение списка файлов Удаление/добавление/переименование файлов Поиск файла по имени

Права доступа к ресурсам [3]

set-user-ID set-group-ID sticky
Исполняемый файл Установить EUID в UID владельца Установить EGID в GID владельца Раньше закреплял загруженную секцию .text в ОП. Сейчас ничего.
Обычный файл - - -
Директория - Все новое содержимое будет иметь группу, как у этой директории Удалять и переименовывать можно только свои файлы

Права доступа к ресурсам [4]

$> 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

Маска прав: три по три бита

Тип файла

Владелец

Группа

Права доступа к ресурсам [5]

$> 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

Права доступа к ресурсам [6]

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

Права доступа к ресурсам [7]

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

$>

Права доступа к ресурсам [8]

Права новых ресурсов вычитают маску 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

Восьмеричная система

Ресурсы и процессы

Процесс:

  • UID, GID,
  • EUID, EGID,
  • SUID, SGID
  • Дополнительные группы ...

Ресурс:

  • Владелец, группа
  • 9 бит маска доступа для владельца, группы и остальных
  • set-user-ID, set-group-ID, sticky

Проверка доступа

  1. Если EUID = 0, любой доступ разрешен
  2. Если EUID = UID владельца, то доступ разрешен по первым трем битам маски прав
  3. Если EGID или любой добавочный GID процесса == GID ресурса, то доступ разрешен по вторым трем битам маски прав
  4. В остальных случаях доступ по третьим трем битам маски прав

Полезные функции

Смена прав

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);

Группы процессов [1]

  • Процессы могут быть объединены в группу
  • Используется терминалом для массовой рассылки сигналов
int
setpgid(pid_t pid, pid_t pgid);

pid_t
getpgid(pid_t pid);

pid_t
getpgrp(void);

int
setpgrp(void);

Группы процессов [2]

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

Сессии [1]

  • Группы процессов объединяются в сессии
  • Сессия может иметь лидера и контролирующий терминал
  • В сессии может быть одна "передовая" группа процессов и много фоновых
  • Терминал рассылает всей передовой группе сигналы SIGINT, SIGQUIT

proc1

proc2

proc3

proc4

proc5

shell

Лидер сессии

Фоновые группы процессов

Передовая группа процессов

proc6

proc7

Сигнал о потере терминала

Сигналы SIGINT, SIGQUIT. Терминальный ввод

Сессии [2]

pid_t
setsid(void);

Создается новая сессия с одной группой. Процесс становится лидером обоих.

  • Не работает, если уже лидер группы
  • У новой сессии нет терминала

Демонизация процесса [1]

Что сложного в создании демона?

  • Нужно выйти из передовой группы процессов, иначе убьет SIGINT, SIGQUIT и т.д.
  • Нужно выйти из сессии, чтобы отключиться от терминала

Демонизация процесса [2]

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() создает новую сессию и группу, и сделает этот процесс их лидером, оставив терминал в старой сессии

Достаточно позвать демонизатор, и любой процесс форкнет от себя демона, а сам завершится

Демонизация процесса [3]

$> 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

Заключение