Brendan wrote:
Hi,
mariuszp wrote:
Is that not what Linux does? The copy_to_user() function just checks the validitiy of the address and calls memcpy() without any locks.
I don't know; but wouldn't be too surprised if Linux is full of stupid security vulnerabilities like that.
For a simple case, imagine a kernel function that copies data from user-space to kernel space; which checks everything (including checking that the data itself is "safe") before copying, then copies the data. Now imagine another thread in the same process (that has good timing) modifies the data in user-space after the kernel has checked it but before the kernel has copied it. Now you've got data in kernel space that kernel has checked and that kernel think is perfectly "safe"; but is actually malicious. Oops.
Cheers,
Brendan
That's why I copy into kernel space (stack or heap)
before checking the data - then using the kernel copy to check for validity etc, and userspace cannot modify this data on the fly.
What is, then, the safe way to copy data to/from userspace? Do note that I employ page faults for load-on-demand, copy-on-write, and all that magic. My current way of copying to/from userspace is as follows:
1. Call a function which ensures that the appropriate permissions (read/write) are marked for the specific mapping in question, and that the address actually belongs to userspace.
2. Do a memcpy()
The memcpy() may then raise page faults which result in load-on-demand or copy-on-write; and that is done safely and the page fault returns to the faulter.
This, however, would not be possible for a multi-threaded process, because another thread could munmap() or mprotect() the mapping in question while the kernel is still copying.
So I thought to resolve it like this:
1. Check if the address is within the userland range.
2. Set a "fault handler" in my Thread structure by calling some function (let's call it catch() ): the catch() function returns 0 and sets the values of registers to restore in the Thread description, for when a page fault occurs.
3. Perform the memcpy(): if a page fault occurs, the "catch registers" are restored, causing a jump-back to step 2, and catch() returns -1.
4. Call uncatch(), which would report that we no longer want to catch the exceptions.
Is there a problem with this approach? How would I "lock pages" as you suggested? With your example, another thread could still modify data half-way through even if I used some kind of spinlock, because userspace cannot be trusted to respect that spinlock.