Tuesday, July 31, 2018

A Linux Kernel Module File Reader

A Kernel Module that Reads the Hosts File Systems

In my previous blog post I discussed reading files from within a kernel module, which remains a controversial topic among kernel developers. I am following through with a kernel module that will read both normal files and virtual files in /proc, /sys, and other sysfs-related file systems.

The source code for this module is located on my GitHub: https://github.com/ncultra/procfs.

Reading Normal Files

Reading normal files is as easy as:

void *buffer = NULL;
loff_t max_size = 0x1000, size = 0;

int ccode = 
kernel_read_file_from_path(name, &buffer, &size, max_size, READING_MODULE);

The result code will be returned by the function, and if successful the contents of the file will be contained in buffer, and the size of the file will be stored in size.

It bears time looking at the last parameter: READING_MODULE is an enumerated value that tells the kernel what and why it is reading. Other enumerated values include READING_FIRMWARE, READING_KEXEC_IMAGE, and READING_X509_CERTIFICATE. (See enum kernel_read_file_id in /include/linux/fs.h.)

The idea is that a kernel module needs to have a very specific reason to open and read a file. Otherwise its easier and safer to open and read a file from a user space program.


Reading Virtual Files

Some entries in the kernel's virtual file systems don't stat normally. (For example  /proc/cpuinfo or /proc/1/mounts.) When you call vfs_getattr the return struct kstat shows the file having zero size and zero blocks. But you know many of the files can be read using system calls because you can cat them from user space.

If you call kernel_read_file_from_path using "/proc/cpuinfo", for example, it will return -EINVAL as follows:

if (!S_ISREG(file_inode(file)->i_mode) || max_size < 0)
return -EINVAL;

if (i_size_read(file_inode(f)) <= 0)

return -EINVAL;




The solution is to call the lower-level kernel_read function in a loop, managing file handles, memory, file size, and errors yourself.

My kernel module does just that with its vfs_read_file function:

ssize_t vfs_read_file(char *name, void **buf, size_t max_count, loff_t *pos)


vfs_read_file loops through the file in chunks, re-allocing memory as is needed. Upon failing to read data, or upon reading the last chunk, it will exit the loop. The result is similar to the earlier kernel_read_file_from_path, except that we must manage the file handle and memory ourselves.

Building and Running procfs.ko

The kernel module accepts module (command-line) parameters for the file name, type, chunk size, and max buffer size. It will simply dump the file contents out into /var/log/messages. The same code is being used in security research project.