Меняем PID процесса в Linux с помощью модуля ядра

в 13:48, , рубрики: C, linux kernel development, ненормальное программирование, Программирование

В этой статье мы попытаемся создать модуль ядра, способный изменить PID уже запущенного процесса в ОС Linux, а так же поэкспериментировать с процессами, получившими измененный PID.
Меняем PID процесса в Linux с помощью модуля ядра - 1

Предупреждение: смена PID — нестандартный процесс, и при определенных обстоятельствах может привести к панике ядра.
Наш тестовый модуль будет реализовывать символьное устройство /dev/test, при чтении с которого процессу будет изменен PID. За пример реализации символьного устройства спасибо этой статье. Полный код модуля приведен в конце статьи. Конечно, самым правильным решением было добавить системный вызов в само ядро, однако это потребует перекомпиляцию ядра.

Окружение

Все действия по тестированию модуля выполнялись в виртуальной машине VirtualBox с 64 битным дистрибутивомLInux и версией ядра 4.14.4-1. Связь с машиной осуществлялась с помощью SSH.

Попытка #1 простое решение

Пару слов о current: переменная current указывает на структуру task_struct с описанием процесса в ядре(PID, UID, GID, cmdline, namespaces и т.д)
Первой идеей было просто поменять параметр current->pid из модуля ядра на нужный.

static ssize_t device_read( struct file *filp,
       char *buffer,
       size_t length,
       loff_t * offset )
{
 printk( "PID: %d.n",current->pid);
 current->pid = 1;
 printk( "new PID: %d.n",current->pid);
 ,,,
}

Для проверки работоспособности модуля я написал программу на C++:

#include <iostream>
#include <fstream>
#include <unistd.h>
int main()
{
    std::cout << "My parent PID "  << getppid() << std::endl;
    std::cout << "My PID "  << getpid() << std::endl;
    std::fstream f("/dev/test",std::ios_base::in);
    if(!f)
    {
        std::cout << "f error";
        return -1;
    }
    std::string str;
    f >> str;
    std::cout << "My new PID "  << getpid() << std::endl;
    execl("/bin/bash","/bin/bash",NULL);
}

Загрузим модуль коммандой insmod, создадим /dev/test и попробуем.
[root@archlinux ~]# ./a.out
My parent PID 293
My PID 782
My new PID 782

PID не изменился. Возможно, это не единственное место, где указывается PID.

Попытка #2 дополнительные поля PID

Если не current->pid является идентификатором процесса, то что является? Быстрый просмотр кода getpid() навел на структуру task_struct, описывающую процесс Linux и файл pid.c в исходном коде ядра. Нужная функция — __task_pid_nr_ns. В коде функции встречается обращение task->pids[type].pid, этот параметр мы и изменим
Компилируем, пробуем
Меняем PID процесса в Linux с помощью модуля ядра - 2
Так как тестировал я по SSH, мне удалось получить вывод программы до падения ядра:
My parent PID 293
My PID 1689
My new PID 1689

Первый результат, уже что-то. Но PID все равно не изменился.

Попытка #3 не экспортируемые символы ядра

Более внимательное изучение pid.c дало функцию, которая делает то, что нам нужно
static void __change_pid(struct task_struct *task, enum pid_type type,
struct pid *new)

Функция принимает задачу, для которой надо изменить PID, тип PID и, собственно, новый PID. Созданием нового PID занимается функция
struct pid *alloc_pid(struct pid_namespace *ns)
Эта функция принимает только пространство имен, в котором будет находиться новый PID, это пространство можно получить с помощью task_active_pid_ns.
Но есть одна проблема: эти символы ядра не экспортируются ядром и не могут использоваться в модулях. В решении этой проблемы мне помогла замечательная статья. Код функции find_sym взят оттуда.

static asmlinkage void (*change_pidR)(struct task_struct *task, enum pid_type type,
		struct pid *pid);
static asmlinkage struct pid* (*alloc_pidR)(struct pid_namespace *ns);
static int __init test_init( void )
{
 printk( KERN_ALERT "TEST driver loaded!n" );
 change_pidR = find_sym("change_pid");
 alloc_pidR = find_sym("alloc_pid");
 ...
}
static ssize_t device_read( struct file *filp,
       char *buffer,
       size_t length,
       loff_t * offset )
{
 printk( "PID: %d.n",current->pid);
 struct pid* newpid;
 newpid = alloc_pidR(task_active_pid_ns(current));
 change_pidR(current,PIDTYPE_PID,newpid);
 printk( "new PID: %d.n",current->pid);
 ...
}

Комплируем, запускаем
My parent PID 299
My PID 750
My new PID 751

PID изменен! Ядро автоматически выделило нашей программе свободный PID. Но можно ли использовать PID, который занял другой процесс, например PID 1? Добавим после аллокации код

newpid->numbers[0].nr = 1;

Комплируем, запускаем
My parent PID 314
My PID 1172
My new PID 1

Получаем настоящий PID 1!
Меняем PID процесса в Linux с помощью модуля ядра - 3
Bash выдал ошибку, из за которой не будет работать переключение задач по комманде %n, но все остальные функции работают отлично.

Интересные особенности процессов с измененным PID

PID 0: войти нельзя выйти

Вернемся к коду и изменим PID на 0.
newpid->numbers[0].nr = 0;
Комплируем, запускаем
My parent PID284
My PID 1517
My new PID 0

Выходит PID 0 не такой и особенный? Радуемся, пишм exit и…
Меняем PID процесса в Linux с помощью модуля ядра - 4
Ядро падает! Ядро определило нашу задачу как IDLE TASK и, увидев завершение, просто упало. Видимо, перед завершением наша программа должна вернуть себе «нормальный» PID.

Процесс-невидимка

Вернемся к коду и выставим PID, гарантированно не занятый
newpid->numbers[0].nr = 12345;
Комплируем, запускаем
My parent PID296
My PID 735
My new PID 12345

Посмотрим, что находится в /proc
1 148 19 224 288 37 79 86 93 consoles fb kcore locks partitions swaps version
10 149 2 226 29 4 8 87 acpi cpuinfo filesystems key-users meminfo sched_debug sys vmallocinfo
102 15 20 23 290 5 80 88 asound crypto fs keys misc schedstat sysrq-trigger vmstat
11 16 208 24 291 6 81 89 buddyinfo devices interrupts kmsg modules scsi sysvipc zoneinfo
12 17 21 25 296 7 82 9 bus diskstats iomem kpagecgroup mounts self thread-self
13 176 210 26 3 737 83 90 cgroups dma ioports kpagecount mtrr slabinfo timer_list
139 18 22 27 30 76 84 91 cmdline driver irq kpageflags net softirqs tty
14 182 222 28 31 78 85 92 config.gz execdomains kallsyms loadavg pagetypeinfo stat uptime

Как видим /proc не определяет наш процесс, даже если мы заняли свободный PID. Предыдущего PID тоже нет в /proc, и это весьма странно. Возможно, мы находимся в другом пространстве имен и поэтому не видны основному /proc. Смонтируем новый /proc, и посмотрим что там
1 14 18 210 25 291 738 81 9 bus devices fs key-users locks pagetypeinfo softirqs timer_list
10 148 182 22 26 296 741 82 90 cgroups diskstats interrupts keys meminfo partitions stat tty
102 149 19 222 27 30 76 83 92 cmdline dma iomem kmsg misc sched_debug swaps uptime
11 15 2 224 28 37 78 84 93 config.gz driver ioports kpagecgroup modules schedstat sys version
12 16 20 226 288 4 79 85 acpi consoles execdomains irq kpagecount mounts scsi sysrq-trigger vmallocinfo
13 17 208 23 29 6 8 86 asound cpuinfo fb kallsyms kpageflags mtrr self sysvipc vmstat
139 176 21 24 290 7 80 87 buddyinfo crypto filesystems kcore loadavg net slabinfo thread-self zoneinfo

По прежнему нашего процесса нет, а значит мы в обычном пространстве имен. Проверим ps -e | grep bash
296 pts/0 00:00:00 bash
Только один bash, с которого мы и запускали программу. Ни предыдущего PID, ни текущего в списке нет.

Ссылка на github

Автор: Gravit

Источник

Поделиться

* - обязательные к заполнению поля