• Uncategorized

About android : Android-MSM-kernel-copytouser-fails

Question Detail

I’m writing a kernel driver for a Linux kernel running on Android devices (Nexus 5X).

I have a kernel buffer and I want to expose a device to read from it. I can read and write from the kernel buffer but I cannot write to the userspace buffer received from the read syscall. The very strange thing is that copy_to_user works only for less than 128 bytes… it makes no sense to me.

The code is the following ( truncated ):

static ssize_t dev_read(struct file *filep, char __user *buffer, size_t len, loff_t *offset){
    unsigned long sent;
    // ...
    pr_err("MYLOGGER: copying from buffer: head=%d, tail=%d, cnt=%d, sent=%lu, access=%lu\n",
          head, tail, cnt, sent,
          access_ok(VERIFY_WRITE, buffer, sent));

   if(sent >= 1) {
       sent -= copy_to_user(buffer, mybuf + tail, sent);
       pr_err("MYLOGGER: sent %lu bytes\n", sent);
       // ...
   }
    // ...
}

The output is the following:

[   56.476834] MYLOGGER: device opened
[   56.476861] MYLOGGER: reading from buffer
[   56.476872] MYLOGGER: copying from buffer: head=5666644, tail=0, cnt=5666644, sent=4096, access=1
[   56.476882] MYLOGGER: sent 0 bytes

As you can see from the log sent is 4096, no integer overflow here.
When using dd I’m able to read up to 128 bytes per call ( dd if=/dev/mylog bs=128 ). I think that when using more than 128 bytes dd uses a buffer from the heap and the kernel cannot access it anymore, which is what I cannot understand.

I’m using copy_to_user from the read syscall handler, I’ve also printed the current->pid and it is the same process.

The kernel sources can be found from google android sources.

The function copy_to_user is defined at arch/arm64/include/asm/uaccess.h and the __copy_to_user can be found in arch/arm64/lib/copy_to_user.S.

Thank you for your time, I hope to get rid of this madness with your precious help.

— EDIT —

I’ve wrote a small snippet to get the vm_area_struct of the destination userspace buffer and I print out the permissions, this is the result:

MYLOGGER: buffer belongs to vm_area with permissions rw-p

So that address should be writable…

— EDIT —

I’ve written more debugging code, logging the state of the memory page used by the userspace buffer.

MYLOGGER: page=(0x7e3782d000-0x7e3782e000) present=1

Long story short it works when the page is present and will not cause a page fault. This is insanely weird, the page fault shall be managed by the virtual memory allocator that would load the page into the main memory…

Question Answer

For some reason, if the page is not present in memory the kernel will not fetch it.

My best guess is the __copy_to_user assembly function exception handler, which returns the number of uncopied bytes.

This exception handler is executed before the virtual memory page fault callback. Thus you won’t be able to write to userspace unless the pages are already present in memory.

My current workaround is to preload those pages using get_user_pages.

I hope that this helps someone else 🙂

The problem was that I held a spin_lock.

copy_{to,from}_user shall never be called while holding a spin_lock.
Using a mutex solves the problem.

I feel so stupid to had wasted days on this…

You may also like...

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.