|
|
Log in / Subscribe / Register

Splicing out vmsplice()

By Jonathan Corbet
June 4, 2026
The splice() and vmsplice() system calls are meant to improve performance for certain data-movement tasks by minimizing (or avoiding altogether) system calls and the copying of data. They also have a long history of security problems. The recent flood of LLM-discovered vulnerabilities has drawn attention, once again, to splice() and vmsplice(); as a result, they may end up being removed altogether.

Some history

Larry McVoy is credited for first raising the idea of a splice() system call that would connect a file directly to a pipe. With the classic POSIX API, an application would copy file data into a pipe with a loop that read chunks of data from the file (thus copying that data into user space), then wrote those chunks to the pipe (copying the data back into the kernel). With a single splice() call, the application could request that the kernel implement that loop, getting the work done with far fewer system calls and less data copying. After years of discussion, a splice() implementation was added in 2006 to the 2.6.17 kernel by Jens Axboe; it looks like:

    ssize_t splice(int fd_in, off_t *off_in, int fd_out, off_t *off_out,
    		   size_t size, unsigned int flags);

It will attempt to copy up to size bytes from fd_in to fd_out; one of the two file descriptors must be a pipe. The return value is the number of bytes actually copied.

vmsplice() was added (by Axboe) shortly thereafter (also in time for 2.6.17):

    ssize_t vmsplice(int fd, const struct iovec *iov, size_t nr_segs, unsigned int flags);

Here, iov is an array of nr_segs iovec structures indicating regions of memory. If fd is a readable pipe file descriptor, data will be read into those memory regions from the pipe. If, instead, fd is writable, the data will move from the memory regions into the pipe. The fact that there is no explicit argument indicating the direction of data movement is one of vmsplice()'s special quirks. Another is that there is no way to know when the data transfer completes and, thus, when it is safe to access the memory given to vmsplice(). The SPLICE_F_GIFT flag "gifts" the indicated memory pages to the kernel; the caller pledges to never touch them again. This option is meant to make zero-copy operations available in some situations.

The implementation of the splice system calls involves a fair amount of complexity within the kernel; it also depends on all kernel subsystems that might receive a spliced buffer to handle it properly. So, arguably, it is not surprising that they have been the focus of a lot of vulnerabilities, including a high-profile exploit (see also this followup article) in 2008. Many of the recently disclosed kernel vulnerabilities involve a combination of these system calls and subsystems that do not handle them correctly.

Protecting read-only files

In mid-May, Pedro Falcato sent a brief patch aimed at making the splice system calls harder to exploit. Specifically, the patch adds a new sysctl knob, fs.splice_needs_write; if that knob is set to a value of one (the default is zero), then it will not be possible to splice() to a file that the calling process lacks the permissions to write to, even if the requested operation is a read from that file that would otherwise be permitted. Similarly, vmsplice() cannot be invoked with memory backed by an unwritable file.

In essence, this patch is an admission of defeat; it is an acknowledgment that the splice system calls simply cannot be implemented in a way that prevents security vulnerabilities. Rather than (continue to) try, the kernel developers would simply be giving administrators the ability to forbid splice operations that might be exploited to give write access to a read-only file. If more such vulnerabilities exist, this change would be a quick way to render them all harmless.

The reactions to the proposal were mixed. Matthew Wilcox said: "I don't have a problem with the idea, other than it's really sad we have to do this". Christian Brauner, though, called it "a knee-jerk reaction to an exploit class originating in buggy modules that we have little control over" and an extension of an already problematic API. Jann Horn suggested that, rather than blocking operations on read-only files, it would be better to degrade the call to an ordinary copy operation. Mateusz Guzik called it "a half-measure which will at best buy few weeks until splice bugs dry out and there will be a new attack vector du jour which people point their LLMs at".

After the discussion had gone on for a few days, Falcato said that the consensus seemed to favor degrading to simple copy operations rather than blocking the system call entirely. There would be a second version of the series forthcoming that took that approach.

Removing vmsplice()

Before that second version could appear, though, Askar Safin showed up with a patch series that takes away the special functionality of vmsplice() entirely. The system call still exists, but the implementation simply copies the data within the kernel rather than attempting to provide complex, zero-copy semantics. In short, a vmsplice() call would be turned into the equivalent preadv2() or pwritev2() call.

Falcato was unimpressed with this development, and suggested that Safin's patches should not even be considered. Brauner had some gentle criticism for the way in which this work was done:

So I think this is a case where no explicit rules have been broken. But if you know that someone has been posting patches and is working on a problem just racing them to get your own stuff merged is very likely to unnecessarily ruffle feathers. So sync with the person next time.

The patches themselves, though, have been reasonably well received. Andy Lutomirski said:

I have no comment on the code or the history. But I'm 100% in favor of the solution. vmsplice is a crappy API, and would be incredibly complex to get the implementation right, and it should be removed. But it has users, and the approach of just mapping them straight to pread/pwrite makes perfect sense.

Linus Torvalds was cautiously in favor of the change; he also suggested making a similar change to splice() if the vmsplice() change does not cause too much anguish. Brauner, for his part, has applied the series with an eye toward merging during the 7.2 development cycle.

That merging should not be seen as a certainty at this point; it is noteworthy that this conversation has happened mostly without the participation of developers who actually use the splice calls. Some of those users are beginning to appear now. Christian Brauner passed on a report of a test regression pointing out a subtle behavior change that can probably be addressed. Willy Tarreau said that he is a heavy user of the splice system calls: "It simply doubles the network bandwidth compared to not using that. (62 Gbps per core vs 31). I would seriously miss it if I couldn't use this anymore." He suggested perhaps further restricting the types of memory that could be passed to vmsplice() (such as only allowing anonymous memory) instead.

So users of the splice system calls do exist, but there seem to be a lot of voices united in their desire to remove the zero-copy logic behind those calls. Torvalds has also indicated a desire to make a similar change to the more widely used sendfile() system call which, he said, was "a mistake". The reimplementation of these system calls should not break any code, since the resulting behavior should look the same from user space, but it does have the possibility of causing performance regressions. That may be enough to prevent these changes from happening in the end. But, as Torvalds said: "I just suspect we'll never get real answers without going the 'let's just see what happens' route". The time has apparently come to see what happens.

Index entries for this article
Kernelsplice()
KernelSystem calls/vmsplice()


The LWN site is currently under high scraper load, so comment display has been suppressed for anonymous users. If you are a human, you may read the comments by clicking the button below:

Note: you can avoid this step in the future by logging into your LWN account.


Copyright © 2026, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds