Kernel space: Details of the vmsplice() exploit

A recent Linux security hole allows local users to seize the power of root. We show the Linux bugs that came together to let it happen

Last week, we published a discussion of the <span style="font-family: Courier, Monospace">vmsplice()</span> exploit which showed how the failure to check permissions for a read operation led to a buffer overflow within the kernel. Subsequently, a linux-kernel reader pointed out that the article stopped short of a complete explanation: this is not an ordinary buffer overflow exploit. This article picks up where the last one left off and describes how the vmsplice() exploit makes use of this buffer overflow to take over the system. When vmsplice() is being used to feed data from memory into a pipe, the function charged with making it all happen is vmsplice_to_pipe(), found in fs/splice.c. It declares a couple of arrays of interest:

struct page *pages[PIPE_BUFFERS]; struct partial_page partial[PIPE_BUFFERS];

PIPE_BUFFERS, remember, is 16 on exploitable configurations. Both of these arrays are passed into get_iovec_page_array(), which, as described in the previous article, makes a call to get_user_pages() to fill in the pages array. As a result of the failure to check whether the calling application is allowed to read the requested region of memory, get_user_pages() will overflow the pages array, writing far more than PIPE_BUFFERS pointers into it. These are, however, pointers to legitimate kernel data structures; it remains to be seen how this overflow enables the attacker to take control of the system.

The partial array is also passed into get_iovec_page_array(); it describes the portion of each page which should be written into the pipe. To that end, a loop like this is run immediately after returning from get_user_pages():

for (i = 0; i < error; i++) { const int plen = min_t(size_t, len, PAGE_SIZE - off); partial[buffers].offset = off; partial[buffers].len = plen; /* ... */ }

Since full pages are being written in this case, the calculated offset will be zero, and the length will be PAGE_SIZE (4096). The value of error is the return value from get_user_pages(); that will be the number of pages actually mapped: 46, in the case of the exploit. Remember that the partial array is also dimensioned to hold 16 entries, so this loop will overflow that array as well.

Join the newsletter!


Sign up to gain exclusive access to email subscriptions, event invitations, competitions, giveaways, and much more.

Membership is free, and your security and privacy remain protected. View our privacy policy before signing up.

Error: Please check your email address.

More about Linux

Show Comments