Change the process PID on Linux using the kernel module
In this article, we will try to create a kernel module that can change the PID of an already running process on Linux, as well as experiment with processes that receive a modified PID.
Warning : changing the PID is a non-standard process and, in certain circumstances, can lead to a kernel panic.
Our test module will implement the character device / dev / test, upon reading from which the process will change its PID. For an example implementation of a character device, thanks to this article. The full module code is given at the end of the article. Of course, the most correct solution was to add a system call to the kernel itself, however this would require recompiling the kernel.
All actions for testing the module were performed in a VirtualBox virtual machine with a 64-bit Linux distribution and kernel version 4.14.4-1. Communication with the machine was carried out using SSH.
A few words about current : the current variable indicates a task_struct structure with a description of the process in the kernel (PID, UID, GID, cmdline, namespaces, etc.) The
first idea was to simply change the current-> pid parameter from the kernel module to the one you need.
To test the functionality of the module, I wrote a program in C ++:
We load the module with the insmod command, create / dev / test and try.
PID has not changed. Perhaps this is not the only place where the PID is indicated.
If not current-> pid is the process identifier, then what is? A quick look at the getpid () code pointed to the task_struct structure describing the Linux process and the pid.c file in the kernel source code. The required function is __task_pid_nr_ns. In the function code, there is a call task-> pids [type] .pid, we will change this parameter.
Compile, try.
Since I tested using SSH, I managed to get the program output before the kernel crashed:
The first result is already something. But the PID still hasn't changed.
A closer look at pid.c gave a function that does what we need. The function accepts a task for which we need to change the PID, type of PID and, in fact, the new PID. The function is creating a new PID. This function accepts only the namespace in which the new PID will be located, this space can be obtained using . But there is one problem: these kernel symbols are not exported by the kernel and cannot be used in modules. A wonderful article helped me solve this problem . The find_sym function code is taken from there.
Compile, run
PID changed! The kernel automatically allocated a free PID to our program. But is it possible to use a PID that another process took, like PID 1? Add code after allocation
Compile, run
We get a real PID 1!
Bash generated an error due to which task switching on the% n command will not work, but all other functions work fine.
Let's go back to the code and change the PID to 0.
We compile, run
It turns out PID 0 is not so special? Rejoice, write exit and ...
The core is falling! The kernel defined our task as IDLE TASK and, upon completion, simply crashed. Apparently, before the end of our program should return to itself a "normal" PID.
Let's go back to the code and set the PID, guaranteed not busy.
We compile, run
Let's see what is in / proc
As you can see, / proc does not define our process, even if we took a free PID. There is no previous PID in / proc either, which is very strange. We may be in a different namespace and therefore not visible to the main / proc. Mount the new / proc, and see what's there
As before, there is no our process, which means that we are in the usual namespace. Let's check
Only one bash, from which we started the program. Neither the previous PID nor the current one is in the list.
Github link
Warning : changing the PID is a non-standard process and, in certain circumstances, can lead to a kernel panic.
Our test module will implement the character device / dev / test, upon reading from which the process will change its PID. For an example implementation of a character device, thanks to this article. The full module code is given at the end of the article. Of course, the most correct solution was to add a system call to the kernel itself, however this would require recompiling the kernel.
Environment
All actions for testing the module were performed in a VirtualBox virtual machine with a 64-bit Linux distribution and kernel version 4.14.4-1. Communication with the machine was carried out using SSH.
Attempt # 1 simple solution
A few words about current : the current variable indicates a task_struct structure with a description of the process in the kernel (PID, UID, GID, cmdline, namespaces, etc.) The
first idea was to simply change the current-> pid parameter from the kernel module to the one you need.
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);
,,,
}
To test the functionality of the module, I wrote a program in C ++:
#include
#include
#include
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);
}
We load the module with the insmod command, create / dev / test and try.
[root@archlinux ~]# ./a.out
My parent PID 293
My PID 782
My new PID 782
PID has not changed. Perhaps this is not the only place where the PID is indicated.
Attempt # 2 additional PID fields
If not current-> pid is the process identifier, then what is? A quick look at the getpid () code pointed to the task_struct structure describing the Linux process and the pid.c file in the kernel source code. The required function is __task_pid_nr_ns. In the function code, there is a call task-> pids [type] .pid, we will change this parameter.
Compile, try.
Since I tested using SSH, I managed to get the program output before the kernel crashed:
My parent PID 293
My PID 1689
My new PID 1689
The first result is already something. But the PID still hasn't changed.
Attempt # 3 non-exportable kernel characters
A closer look at pid.c gave a function that does what we need. The function accepts a task for which we need to change the PID, type of PID and, in fact, the new PID. The function is creating a new PID. This function accepts only the namespace in which the new PID will be located, this space can be obtained using . But there is one problem: these kernel symbols are not exported by the kernel and cannot be used in modules. A wonderful article helped me solve this problem . The find_sym function code is taken from there.
static void __change_pid(struct task_struct *task, enum pid_type type,
struct pid *new)
struct pid *alloc_pid(struct pid_namespace *ns)
task_active_pid_ns
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);
...
}
Compile, run
My parent PID 299
My PID 750
My new PID 751
PID changed! The kernel automatically allocated a free PID to our program. But is it possible to use a PID that another process took, like PID 1? Add code after allocation
newpid->numbers[0].nr = 1;
Compile, run
My parent PID 314
My PID 1172
My new PID 1
We get a real PID 1!
Bash generated an error due to which task switching on the% n command will not work, but all other functions work fine.
Interesting features of processes with a modified PID
PID 0: cannot log in
Let's go back to the code and change the PID to 0.
newpid->numbers[0].nr = 0;
We compile, run
My parent PID284
My PID 1517
My new PID 0
It turns out PID 0 is not so special? Rejoice, write exit and ...
The core is falling! The kernel defined our task as IDLE TASK and, upon completion, simply crashed. Apparently, before the end of our program should return to itself a "normal" PID.
Invisibility process
Let's go back to the code and set the PID, guaranteed not busy.
newpid->numbers[0].nr = 12345;
We compile, run
My parent PID296
My PID 735
My new PID 12345
Let's see what is in / 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
As you can see, / proc does not define our process, even if we took a free PID. There is no previous PID in / proc either, which is very strange. We may be in a different namespace and therefore not visible to the main / proc. Mount the new / proc, and see what's there
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
As before, there is no our process, which means that we are in the usual namespace. Let's check
ps -e | grep bash
296 pts/0 00:00:00 bash
Only one bash, from which we started the program. Neither the previous PID nor the current one is in the list.
Github link