How You Can Change A PID process in Linux By Using A Kernel Module

LINUX PID MAIN PHOTO

How You Can Change A PID process in Linux By Using A Kernel Module

In this article, we will try to create a kernel module that can change the PID of an already running process in the Linux OS, and also experiment with the processes that received the modified PID.

LINUX PID 1

Warning: Changing PID is a non-standard process, and under certain circumstances can lead to kernel panic.

Our test module will implement the character device/ dev/test when reading from which the process will be changed PID. For an example of implementing a character device, thanks to this article. The complete code of the module is given at the end of the article. Of course, the correct solution was to add a system call to the kernel itself, but this would require a recompilation of the kernel.

Environment

All the module testing activities were performed in a VirtualBox virtual machine with a 64-bit Linux distribution and a 4.14.4-1 kernel version. Communication with the machine was carried out using SSH.

Attempt # 1 simple solution

A couple of words about current: the current variable points to a task_struct structure with a process described in the kernel (PID, UID, GID, cmdline, namespaces, etc.

The first idea was simply to change the current-> pid parameter from the kernel module to the desired one.


static size_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 <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);
}

Load the module with the command instead, create/dev/test and try.


[root@archilinux ~]# ./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 ID, what is it? A quick scan of the code getpid () pointed to a task_struct structure that describes the Linux process and the pid.c file in the kernel source code. The desired function is __task_pid_nr_ns. In the function code there is a call to task-> pids [type] .pid, this parameter we will change.

Let’s compile it and try again.

LINUX PID 2

Since we tested on SSH, we were able to get the output of the program before the kernel crashed:


My parent PID 293
My PID 1689
My new PID 1689

The first result is already something. But PID still has not changed.

Attempt # 3 non-exportable kernel symbols

A more careful study of pid.c gave a function that does what we need
static void __change_pid (struct task_struct * task, enum pid_type type,
struct pid * new)
The function takes a task for which it is necessary to change the PID, the PID type and, in fact, the new PID. The creation of a new PID is handled by the function
struct pid * alloc_pid (struct pid_namespace * ns)

This function accepts only the namespace in which the new PID will reside, this space can be obtained using task_active_pid_ns.
But there is one problem: these kernel symbols are not exported by the kernel and can not be used in modules. In solving this problem I was helped by a wonderful article. The function code find_sym is taken from there.


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

Let’s compile it and try again.


My parent PID 299
My PID 750
My new PID 751

Now PID has been changed! The kernel automatically allocated our program a free PID. But is it possible to use PID, which took another process, for example, PID 1? We add after the allocation code


My parent PID 314
My PID 1172
My new PID 1

Let’s compile it and try again.


My parent PID 314
My PID 1172
My new PID 1

Get the real PID 1.

LINUX PID 3

Bash has issued an error that prevents the switching of tasks on the% n command, but all other functions work fine.

Interesting features of processes with a modified PID

PID 0: cannot be logged out

Let’s return to the code and change the PID to 0.


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

Compiling and run it.


My parent PID284
My PID 1517
My new PID 0

Is PID 0 not so special? Now let’s try to exit.

LINUX PID 4

The core falls! The kernel defined our task as IDLE TASK and, seeing the completion, just fell. Apparently, before the completion of our program should return a “normal” PID.

The invisible process

Let’s return to the code and expose the PID, guaranteed not to be occupied


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

Compiling and run it.


My parent PID296
My PID 735
My new PID 12345

Let’s see what’s in the /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 occupied a free PID. The previous PID also does not exist in /proc, and this is very strange. Perhaps we are in a different namespace and therefore are not visible to the main /proc. We’ll 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, our process does not exist, which means that we are in the usual namespace. Let’s check


ps -e | grep bash
296 pts/0 00:00:00 bash

Summary

Only one bash, from which we started the program. There is no previous PID or current one on the list.