Всем привет!
Буду делать механизм, позволяющий в userspace одному процессу относительно безопасно прыгать между заранее определенными uid’ами и gid’ами.
Должно выйти что-то типа такого:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
int main(void) { uid_t uids[] = {99,1000,1001,1002,1003}; /* root */ allow_setuid(5, uids); /* nobody */ setuid(99); while (1) { // ... /* user1002 */ setuid(1002); // ... /* back to nobody */ setuid(99); } return 0; } |
К сожалению CAP_SETUID/CAP_SETGID не позволяют ограничить список пользователей, поэтому буду делать модуль ядра.
Назову это как-нибудь ужасно, например ugidctl.c
Итак, поехали:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/* * This file is released under the GPL. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Rommer <rommer@ibuffed.com>"); static int __init init_ugidctl(void) { return 0; } static void __exit exit_ugidctl(void) { } module_init(init_ugidctl); module_exit(exit_ugidctl); |
Ещё понадобится какой-нибудь простенький Makefile для модуля:
1 2 3 4 5 6 7 |
obj-m += ugidctl.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean |
Самое первое и важное — собственно научиться переключать идентификаторы пользователей, групп и дополнительных групп. За это отвечают 3 системных вызова в ядре: setuid(2), setgid(2) и setgroups(2) соответственно. Но просто так в модуле их вызвать нельзя — современные ядра для модулей экспортируют только несколько системных вызовов (например sys_close). Сами функции в ядре, естественно, присутствуют:
1 2 3 4 |
# grep -E 'sys_set(uid|gid|groups)$' /proc/kallsyms ffffffff81089070 T sys_setgid ffffffff810892e0 T sys_setuid ffffffff8109f700 T sys_setgroups |
Просто открыть в модуле ядра /proc/kallsyms и поискать там нужные адреса мне тоже никто не даст, да и глупо это как-то. Зато есть kallsyms_on_each_symbol. Эта функция позволяет проходиться по всем символам ядра, вызывая пользовательскую функцию для каждой записи. Подробнее можно почитать тут. Вот и поищем этой функцией:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
/* * This file is released under the GPL. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/kallsyms.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Rommer <rommer@ibuffed.com>"); #define MOD_NAME "ugidctl" /* not exported syscalls */ static asmlinkage long(*ugidctl_sys_setuid)(uid_t); static asmlinkage long(*ugidctl_sys_setgid)(gid_t); static asmlinkage long(*ugidctl_sys_setgroups)(int, gid_t __user *); static int symbol_walk_callback(void *data, const char *name, struct module *mod, unsigned long addr) { /* Skip the symbol if it belongs to a module */ if (mod) return 0; if (strcmp(name, "sys_setuid") == 0) { if (ugidctl_sys_setuid) { printk(KERN_ERR "%s: duplicate sys_setuid found", MOD_NAME); return -EFAULT; } ugidctl_sys_setuid = (void *)addr; return 0; } if (strcmp(name, "sys_setgid") == 0) { if (ugidctl_sys_setgid) { printk(KERN_ERR "%s: duplicate sys_setgid found", MOD_NAME); return -EFAULT; } ugidctl_sys_setgid = (void *)addr; return 0; } if (strcmp(name, "sys_setgroups") == 0) { if (ugidctl_sys_setgroups) { printk(KERN_ERR "%s: duplicate sys_setgroups found", MOD_NAME); return -EFAULT; } ugidctl_sys_setgroups = (void *)addr; return 0; } return 0; } static int find_syscalls(void) { int rc; ugidctl_sys_setuid = NULL; ugidctl_sys_setgid = NULL; ugidctl_sys_setgroups = NULL; rc = kallsyms_on_each_symbol(symbol_walk_callback, NULL); if (rc) return rc; if (ugidctl_sys_setuid == NULL) { printk(KERN_ERR "%s: unable to find sys_setuid()\n", MOD_NAME); return -EFAULT; } if (ugidctl_sys_setgid == NULL) { printk(KERN_ERR "%s: unable to find sys_setgid()\n", MOD_NAME); return -EFAULT; } if (ugidctl_sys_setgroups == NULL) { printk(KERN_ERR "%s: unable to find sys_setgroups()\n", MOD_NAME); return -EFAULT; } return 0; } static int __init init_ugidctl(void) { int rc; rc = find_syscalls(); if (rc) return rc; printk(KERN_INFO "found sys_setuid() 0x%p", ugidctl_sys_setuid); printk(KERN_INFO "found sys_setgid() 0x%p", ugidctl_sys_setgid); printk(KERN_INFO "found sys_setgroups() 0x%p\n", ugidctl_sys_setgroups); return 0; } static void __exit exit_ugidctl(void) { } module_init(init_ugidctl); module_exit(exit_ugidctl); |
Компилим, запускаем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ make make -C /lib/modules/3.10.0-229.14.1.el7.x86_64/build M=/home/user/tmp modules make[1]: Entering directory `/usr/src/kernels/3.10.0-229.14.1.el7.x86_64' CC [M] /home/user/tmp/ugidctl.o Building modules, stage 2. MODPOST 1 modules CC /home/user/tmp/ugidctl.mod.o LD [M] /home/user/tmp/ugidctl.ko make[1]: Leaving directory `/usr/src/kernels/3.10.0-229.14.1.el7.x86_64' $ sudo insmod ugidctl.ko $ dmesg | tail -3 [89459.608772] found sys_setuid() 0xffffffff810892e0 [89459.608775] found sys_setgid() 0xffffffff81089070 [89459.608777] found sys_setgroups() 0xffffffff8109f700 $ grep -E 'sys_set(uid|gid|groups)$' /proc/kallsyms ffffffff81089070 T sys_setgid ffffffff810892e0 T sys_setuid ffffffff8109f700 T sys_setgroups $ sudo rmmod ugidctl $ |
Работает как и задумывалось.