xorl %eax, %eax

GRKERNSEC_KMEM /dev/{k,}mem Write Protection

leave a comment »

Quite obvious from its name but for the sake of knowledge, here is the description of this grsecurity option:

config GRKERNSEC_KMEM
	bool "Deny writing to /dev/kmem, /dev/mem, and /dev/port"
	help
	  If you say Y here, /dev/kmem and /dev/mem won't be allowed to
	  be written to via mmap or otherwise to modify the running kernel.
	  /dev/port will also not be allowed to be opened. If you have module
	  support disabled, enabling this will close up four ways that are
	  currently used  to insert malicious code into the running kernel.
	  Even with all these features enabled, we still highly recommend that
	  you use the RBAC system, as it is still possible for an attacker to
	  modify the running kernel through privileged I/O granted by ioperm/iopl.
	  If you are not using XFree86, you may be able to stop this additional
	  case by enabling the 'Disable privileged I/O' option. Though nothing
	  legitimately writes to /dev/kmem, XFree86 does need to write to /dev/mem,
	  but only to video memory, which is the only writing we allow in this
	  case.  If /dev/kmem or /dev/mem are mmaped without PROT_WRITE, they will
	  not be allowed to mprotect it with PROT_WRITE later.
	  It is highly recommended that you say Y here if you meet all the
	  conditions above.

So, I believe that all the Linux people know pretty well that /dev/mem and /dev/kmem are widely used for injecting code into the running kernel (most of the times rootkits). By disabling this you make it more difficult, although not impossible to get your system backdoored by a rootkit. To do this, write_mem() (which is used for writing to /dev/mem as its name implies) of drivers/char/mem.c is patched.

static ssize_t write_mem(struct file *file, const char __user *buf,
                         size_t count, loff_t *ppos)
{
        unsigned long p = *ppos;
        ssize_t written, sz;
        unsigned long copied;
        void *ptr;

 	if (!valid_phys_addr_range(p, count))
 		return -EFAULT;
 
#ifdef CONFIG_GRKERNSEC_KMEM
	gr_handle_mem_write();
	return -EPERM;
#endif
    ...
        return written;
}

The call to gr_handle_mem_write() is a simple logging routine from grsecurity/grsec_mem.c:

void
gr_handle_mem_write(void)
{
	gr_log_noargs(GR_DONT_AUDIT, GR_MEM_WRITE_MSG);
	return;
}

that uses the generic logging function gr_log_varargs() as you can read here:

#define gr_log_noargs(audit, msg) gr_log_varargs(audit, msg, GR_NOARGS)

This means that in any case write_mem() is called it’ll not attempt to open /dev/mem for writing, but it will print some message in the system’s log regarding the issue and then immediately return with ‘-EPERM’ (Permission Denied) making it impossible to access that device through this call.
Next, the same technique is applied in the equivalent /dev/kmem writing function…

/*
 * This function writes to the *virtual* memory as seen by the kernel.
 */
static ssize_t write_kmem(struct file *file, const char __user *buf,
                          size_t count, loff_t *ppos)
{
        unsigned long p = *ppos;
        ssize_t wrote = 0;
        ssize_t virtr = 0;
        char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */
        int err = 0;

#ifdef CONFIG_GRKERNSEC_KMEM
	gr_handle_kmem_write();
	return -EPERM;
#endif
    ...
        return virtr + wrote ? : err;
}

A user could obviously use mmap(2) system call to map the required portion of memory as writable and simply inject his code by writing to that mapped area. To avoid this mmap_mem() which can be found at drivers/char/mem.c as well was patched.

static int mmap_mem(struct file *file, struct vm_area_struct *vma)
{
         size_t size = vma->vm_end - vma->vm_start;
    ...
#ifdef CONFIG_GRKERNSEC_KMEM
	if (gr_handle_mem_mmap(vma->vm_pgoff << PAGE_SHIFT, vma))
		return -EPERM;
#endif
    ...
        return 0;
}

Here gr_handle_mem_mmap() is used which is defined at grsecurity/grsec_mem.c like this:

int
gr_handle_mem_mmap(const unsigned long offset, struct vm_area_struct *vma)
{
	unsigned long start, end;

	start = offset;
	end = start + vma->vm_end - vma->vm_start;

	if (start > end) {
		gr_log_noargs(GR_DONT_AUDIT, GR_MEM_MMAP_MSG);
		return -EPERM;
	}

	/* allowed ranges : ISA I/O BIOS */
	if ((start >= __pa(high_memory))
#if defined(CONFIG_X86) || defined(CONFIG_PPC)
	    || (start >= 0x000a0000 && end <= 0x00100000)
	    || (start >= 0x00000000 && end <= 0x00001000)
#endif
	)
		return 0;

	if (vma->vm_flags & VM_WRITE) {
		gr_log_noargs(GR_DONT_AUDIT, GR_MEM_MMAP_MSG);
		return -EPERM;
	} else
		vma->vm_flags &= ~VM_MAYWRITE;

	return 0;
}

This is nothing more than a range checking routine. It will initially check that the passed virtual memory address space is valid and then continue to check if this is inside the ISA I/O BIOS address range. The write operations to that address space will be allowed since the function returns ‘0’. Otherwise, if the VMA’s flag is set to writable it will return with “Permission Denied” error code. Finally, in any other case it will update the virtual memory address’ flag to ‘VM_MAYWRITE’ in order to avoid code injection by utilizing mprotect(2) and it’ll return ‘0’.
At last, /dev/port is another well known device file used in code injection. This device is used to perform operations similar to those of /dev/mem and /dev/kmem to the I/O ports of the system. Since this is another code injection vector, grsecurity patches open_port() like this:

static int open_port(struct inode * inode, struct file * filp)
{
#ifdef CONFIG_GRKERNSEC_KMEM
	gr_handle_open_port();
	return -EPERM;
#endif

        return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
}

Where gr_handle_open_port() as you might have guessed is another logging function.

void
gr_handle_open_port(void)
{
	gr_log_noargs(GR_DONT_AUDIT, GR_PORT_OPEN_MSG);
	return;
}

This means that calls to open_port() will result in logging of the request and exiting with “Permission Denied” error. However, in Linux kernel you could also find another call that was leading to open_port():

#define read_full       read_zero
#define open_mem	open_port
#define open_kmem	open_mem

As you can see, open_mem() is equal to calling open_port(). Since /dev/mem should be accessible for reading by privileged processes the following routine was added and the above definition of open_mem() was removed.

static int open_mem(struct inode * inode, struct file * filp)
{
 	return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
 }

This will allow processes with ‘CAP_SYS_RAWIO’ (System Raw I/O) POSIX capability to open the device. Otherwise it will also return with “Permission Denied”.

Written by xorl

November 9, 2010 at 04:21

Posted in grsecurity, linux, security

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s