LWN: Comments on "Process creation in io_uring" https://lwn.net/Articles/1002371/ This is a special feed containing comments posted to the individual LWN article titled "Process creation in io_uring". en-us Thu, 30 Oct 2025 01:38:11 +0000 Thu, 30 Oct 2025 01:38:11 +0000 https://www.rssboard.org/rss-specification lwn@lwn.net Fork/exec Did a Job Simply & Well - Most of the Problems Mentioned Have Untreated Causes https://lwn.net/Articles/1015335/ https://lwn.net/Articles/1015335/ gstrauss <div class="FormattedComment"> A bit late to the party, similar to @roblucid<br> <p> If the problem is that fork()/execve() is expensive **for large processes** with many memory mappings, file descriptors, etc, then a solution is for those expensive monolithic processes to avoid fork().<br> <p> One user-space solution: a large process could use AF_UNIX sockets to contact a lightweight process-creation daemon already pre-configured to be ready to do some minimal setup, vfork() or clone(), and then execve(). The lightweight process-creation daemon does not even have to be running all the time. It could conceivably be started on-demand via xinetd or systemd socket triggers.<br> <p> I wrote one such user-space process-creation daemon over a decade ago, called `proxyexec` (<a rel="nofollow" href="https://github.com/gstrauss/bsock">https://github.com/gstrauss/bsock</a>)<br> A heavyweight or differently-privileged process can contact proxyexec on an AF_UNIX socket and then pass argv, env, and fds for stdin, stdout, stderr fds. proxyexec can even run under different credentials from the heavyweight process, and can even run in a different container connected by the AF_UNIX named socket. This is not theoretical: an earlier version of proxyexec was (and maybe still is) used by CloudLinux to remove setuid binaries from their containers, and to still provide privileged services -- running under different user accounts and outside the containers -- via proxyexec.<br> <p> tl;dr: Alternative user-space application designs, e.g. possibly using a service oriented architecture (directly or via a process-execution proxy) should be compared and contrasted with extending io_uring to provide IORING_OP_CLONE, IORING_OP_EXEC.<br> </div> Tue, 25 Mar 2025 09:38:03 +0000 Fork/exec Did a Job Simply & Well - Most of the Problems Mentioned Have Untreated Causes https://lwn.net/Articles/1007710/ https://lwn.net/Articles/1007710/ Cyberax <div class="FormattedComment"> <span class="QuotedText">&gt; A better question is why are huge monolithic programs with masses of VM mappings forking anyway? Why aren't small main programs, setting up co-operating processes which have memory isolation and then can fire up threads for their heavy weight processing?</span><br> <p> Why would you reinvent threading and shared memory?<br> </div> Mon, 03 Feb 2025 18:47:42 +0000 Fork/exec Did a Job Simply & Well - Most of the Problems Mentioned Have Untreated Causes https://lwn.net/Articles/1007575/ https://lwn.net/Articles/1007575/ roblucid <div class="FormattedComment"> Modern developers criticising an early 70's design, which gave clean &amp; simple primitives solving problems shouldn't blame fork/exec <br> but the failure to establish clearly better alternatives as the computing environment changed. The multi-user machines of the day were continually swapping out whole processes to disk and reloading them, single processes DID not have vmtables. The fork(2) model allowed multi-tasking and the parent in the child process can set to NULL everything the child does not require with a small amount of memory copying and then overlay its own code via exec(2).<br> <p> Environment solved the problem of child processes having to know about everything that could be set, so banishing that uncessarily, incurs a future maintenance problem. Just imagine if a DB style solution for user preferences hand been imposed with a single point of failure. Having used alternative OS solutions, vast amounts of variables had to be set up and constantly reset in practice, rather than just using a bit copy of an area in process memory.<br> <p> A better question is why are huge monolithic programs with masses of VM mappings forking anyway? Why aren't small main programs, setting up co-operating processes which have memory isolation and then can fire up threads for their heavy weight processing?<br> Surely that's what you want for cache effieciency, so parts with tightly coupled gang processing can share L3, while loosely coupled parts can be scheduled seperately.<br> <p> Seems to me the issue is the problem is the coordination between the program pieces, effectively an efficient message passing system so loosely coupled parts of a program can avoid sharing address spaces.<br> <p> When I read about clone being inefficient because it "copies the whole VM which is mostly unused", I see poor application architecture.<br> Again people mentioned graphics libraries starting threads when linked, well that's what dynamic loading avoids.<br> <p> When applications are statically linking huge amounts of common library code in multiple copies, that then may use threading which requires understanding of a memory model and safe operations, all in ONE big pot, it's a bit rich to moan about inefficiency caused by copying VM tables.<br> Who created this huge memory management problem putting everything in one large pot?<br> <p> Browsers moved away from single process because of security requirements despite it causing duplication, they still manage to be snappy enough, even if it's NOT what you would do in a twitch shooter game.<br> </div> Mon, 03 Feb 2025 12:17:28 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1004863/ https://lwn.net/Articles/1004863/ mrugiero <div class="FormattedComment"> I believe it would be easier (but would take more execs) to just use execline on your first exec to set up the environment correctly. Nice little scripting language which is already designed for that.<br> </div> Sun, 12 Jan 2025 18:17:53 +0000 Re: empty shell https://lwn.net/Articles/1004845/ https://lwn.net/Articles/1004845/ chexo4 <div class="FormattedComment"> Do you think we'll get there at some point? What do you mean by aversion, exactly? This seems like a really useful mechanism to me and I don't see why it wouldn't be a great option to have for the many cases where you don't want/need to inherit the parent process' address space.<br> </div> Sun, 12 Jan 2025 02:33:28 +0000 BPF! https://lwn.net/Articles/1003859/ https://lwn.net/Articles/1003859/ surajm <div class="FormattedComment"> That sounds like the fuchsia api. I'm sure it's popular on many microkernels as well.<br> </div> Tue, 31 Dec 2024 15:19:46 +0000 BPF! https://lwn.net/Articles/1003825/ https://lwn.net/Articles/1003825/ izbyshev <div class="FormattedComment"> <span class="QuotedText">&gt; Thus one of Go's performance secrets is to use posix_spawn() since 2017.</span><br> <p> On Linux, Go uses raw syscalls for almost all standard functionality, and Go programs usually don't even link to a C library. So, Go uses a vfork() equivalent followed by execve(), not posix_spawn() library function [1].<br> <p> <span class="QuotedText">&gt; Linux Java from fork() into posix_spawn() near 2018</span><br> <p> No, it migrated from vfork() [2] (which has been used by default since forever). So, overcommit issues weren't present in the first place.<br> <p> The CPython issue [3] for migrating subprocess from fork() to vfork() contains a lot of useful links on the topic. In some parts, it's outdated [4].<br> <p> [1] <a href="https://github.com/golang/go/blob/194de8fbfaf4c3ed54e1a3c1b14fc67a830b8d95/src/syscall/exec_linux.go#L305">https://github.com/golang/go/blob/194de8fbfaf4c3ed54e1a3c...</a><br> [2] <a href="https://github.com/openjdk/jdk/commit/e21cb12d358c22350cb18f0c656dd375f12665a9">https://github.com/openjdk/jdk/commit/e21cb12d358c22350cb...</a><br> [3] <a href="https://github.com/python/cpython/issues/80004">https://github.com/python/cpython/issues/80004</a><br> [4] <a href="https://github.com/python/cpython/issues/113117">https://github.com/python/cpython/issues/113117</a><br> </div> Tue, 31 Dec 2024 07:37:23 +0000 BPF! https://lwn.net/Articles/1003725/ https://lwn.net/Articles/1003725/ bluca <div class="FormattedComment"> <span class="QuotedText">&gt; Thus one of Go's performance secrets is to use posix_spawn() since 2017.</span><br> <p> I switched systemd to use pidfd_spawn (which is posix_spawn but with clone3(), which gets back a pidfd instread of a pid, and to clone into the target cgroup atomically) in v255 last year for similar reasons, as the copy-on-write trap overhead was hitting hard the azure fleet. I should probably do a write up about that at some point...<br> </div> Sun, 29 Dec 2024 19:17:39 +0000 BPF! https://lwn.net/Articles/1003721/ https://lwn.net/Articles/1003721/ ma4ris8 <div class="FormattedComment"> Here is Rust Maelstrom analysis of the memory usage overhead in fork().<br> It shows, how the overhead rises, when caller process has bigger memory mapping.<br> https://maelstrom-software.com/blog/spawning-processes-on-linux/<br> <p> <p> </div> Sun, 29 Dec 2024 18:56:10 +0000 BPF! https://lwn.net/Articles/1003719/ https://lwn.net/Articles/1003719/ ma4ris8 <div class="FormattedComment"> Gitaly's experience of fork overhead was an amazing read:<br> <a href="https://about.gitlab.com/blog/2018/01/23/how-a-fix-in-go-19-sped-up-our-gitaly-service-by-30x/">https://about.gitlab.com/blog/2018/01/23/how-a-fix-in-go-...</a><br> <p> Thus one of Go's performance secrets is to use posix_spawn() since 2017.<br> <p> Linux Java from fork() into posix_spawn() near 2018, using "jspawnhelper" as a clean up process:<br> <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8212828">https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-...</a><br> <p> Oracle's Java for Solaris took the change earlier, in 2013:<br> <a href="https://bugs.java.com/bugdatabase/view_bug?bug_id=5049299">https://bugs.java.com/bugdatabase/view_bug?bug_id=5049299</a><br> <p> Rust language: glibc uses sometimes posix_spawn():<br> <a href="https://kobzol.github.io/rust/2024/01/28/process-spawning-performance-in-rust.html">https://kobzol.github.io/rust/2024/01/28/process-spawning...</a><br> <p> Fork/Exec major problem:<br> - Memory overcommit (Gitaly article): large programs clone resources during fork. After exec,<br> memory have to be cleaned up by the Kernel, and recycled for re-use.<br> <p> Fork/Exec benefits:<br> - Threaded process: After fork, file descriptor set is vague. Forked process can investigate and clean up the state,<br> so that process doing exec() does not need to know about the caller process.<br> - This benefit is actually a work around: why to leak file descriptors, just to search for, and remove those before exec()?<br> - Could forking thread simply enlist the interesting set of fds, and then skip copying the uninteresting ones?<br> <p> posix_spawn() design:<br> - New process uses parent's memory. No memory overcommit.<br> Caller thread sleeps, until new process does "exec()".<br> New process's thread must do as little work as possible, and, and exec into middle process.<br> Caller thread continues.<br> Middle helper process (jspawnhelper) closes leaked file descriptors, re-maps stdin,stdout,stderr,<br> and then exec's to the final process.<br> - This also copies all file descriptors, thus the fd clean up must be done with a helper program.<br> Speed increase comes from avoiding the memory "Copy on Write" work though for big memory programs.<br> <p> Thus the optimal way (bpf, io_uring) solution would be to declare, what needs to be cloned, re-mapped,<br> and changed. Best is, if nothing unnecessary need to be created, and then destroyed (by middle process).<br> <p> The world for big memory programs (Go, Java), has already moved from fork() into posix_spawn.<br> <p> Thus there is big amount of Kernel work to be avoided, if the io_uring approach (and/or BPF enhancement)<br> can be used to avoid first cloning resources, just to tearing those down, and to call a child process<br> with given arguments, re-mapping stdin, stdout, stderr, passing some extra file descriptors (individual, tail range),<br> and setting child process working directory.<br> <p> So instead of cloning everything, and tearing down, and making caller thread to sleep until child process is launched,<br> we could have something simple, which defines (declarative, programmatically, hybrid of those) the configuration for <br> a new process, and does a clean process launch with the single (0-1) io_uring queue submit,<br> without enforcing caller thread to sleep, until child process is launched.<br> <p> </div> Sun, 29 Dec 2024 18:42:05 +0000 Generalizing system calls to operate on other processes https://lwn.net/Articles/1003690/ https://lwn.net/Articles/1003690/ epa <div class="FormattedComment"> I imagined it would first return from fork() in the parent process, returning zero so you think you are in the child process. But now, any call to open() is actually open_pidfd() applied to the child. And exec() is also redirected so that it calls exec_pidfd() for the child and then jumps back to the end of the fork() call, this time returning the child's pid, so the parent continues executing. That could maybe be done with setjmp/longjmp or with some even darker magic that the C standard library is able to perform, perhaps with the help of inline assembly.<br> <p> I wouldn't be surprised if similar hacks have existed to help port Unix code to single-tasking operating systems like MS-DOS.<br> </div> Sat, 28 Dec 2024 20:58:05 +0000 Generalizing system calls to operate on other processes https://lwn.net/Articles/1003648/ https://lwn.net/Articles/1003648/ daroc <div class="FormattedComment"> You can write your own magical twice-returning functions with setjmp() and longjmp(), although (as always in C) there are caveats around using those correctly.<br> </div> Sat, 28 Dec 2024 14:11:11 +0000 Generalizing system calls to operate on other processes https://lwn.net/Articles/1003646/ https://lwn.net/Articles/1003646/ mathstuf <div class="FormattedComment"> <span class="QuotedText">&gt; Indeed you could even have a fork emulation layer in the C library which, on returning from fork() or vfork(), acts as though you were in the child process, so that calling open() actually calls into open_pidfd(), and then the final exec() call runs exec_pidfd() and then continues with the parent process's code.</span><br> <p> How would that work? The "magic" of `fork()` (and related functions) is that it returns twice: once with a `0` return value and once with a `pid`. How would a library do any kind of emulation to allow taking *both* sides of the `if` condition it (eventually) leads to without some kind of language magic?<br> </div> Sat, 28 Dec 2024 14:01:42 +0000 Generalizing system calls to operate on other processes https://lwn.net/Articles/1003613/ https://lwn.net/Articles/1003613/ epa <div class="FormattedComment"> That's a great read. I particularly liked the idea of making a system call that would apply to another process. So all system calls get an additional process id argument (or a pidfd, I guess) and, where reasonably possible, you are allowed to call them to apply to another process, as long as it's one of your children and executing as the same user, or you are root.<br> <p> That means instead of fork() and in the child process opening file handles, the parent process could take care of all this. Create the child, which is initially not schedulable, make any system calls you want to set up the child's execution environment, then finally an exec_pidfd() to apply to the child process and mark it schedulable. That's a great way to apply "the Unix philosophy", composing the existing simple system calls rather than creating a kitchen sink like posix_spawn(), while avoiding the known problems of forking.<br> <p> Existing code should be translatable to the new scheme without too much trouble. (Indeed you could even have a fork emulation layer in the C library which, on returning from fork() or vfork(), acts as though you were in the child process, so that calling open() actually calls into open_pidfd(), and then the final exec() call runs exec_pidfd() and then continues with the parent process's code. That's a bit kooky but might be a quick way to migrate older code which just wants to spawn a subprocess.)<br> </div> Fri, 27 Dec 2024 17:01:11 +0000 BPF! https://lwn.net/Articles/1003566/ https://lwn.net/Articles/1003566/ alkbyby <div class="FormattedComment"> I predict this will be an interesting discussion.<br> <p> Can you please elaborate more specifically on thread-unsafety of posix_spawn implementations? POSIX might be not explicitly saying that posix_spawn is safe to use in MT programs, but it's main purpose is clearly to fix fork's problems with threads. So it has to be MT-safe.<br> <p> Fork+exec and threads are too unsafe in practice. Even our esteemed editor made an error above. Here: "details of what happens between those two calls can vary quite a bit. &lt;skiped&gt;environment adjusted, and so on".<br> <p> Thing is, updating process environment (e.g. via setenv) typically invokes malloc. And calling malloc in-between fork and exec is unsafe.<br> <p> As per posix (quoting from man 3posix fork): "If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called."<br> <p> In practice malloc implementations go to some lengths to make malloc() possible after fork by carefully setting up pthread_atfork or alternatives. But this is big enough can of worms. And for example "abseil" tcmalloc explicitly doesn't (<a href="https://github.com/search?q=repo%3Agoogle%2Ftcmalloc%20atfork&amp;type=issues">https://github.com/search?q=repo%3Agoogle%2Ftcmalloc%20at...</a>). As per Google's internal policy pthread_atfork is forbidden (which is another, but somewhat related topic).<br> <p> So posix_spawn is the right thing IMO. And any exotic process setup things that might be missing in your favorite libc (e.g. stuff like unshare/prctl) you can always do in a small helper program. You exec into it. It gets clean slate, can do whatever syscalls and mallocs and what not. Single-threadly. And then exec into real thing.<br> <p> As for original discussion, I am really hoping io_uring is kept only for perf-critical stuff. Spawning childs isn't.<br> <p> </div> Fri, 27 Dec 2024 01:52:29 +0000 Chained operations in io_uring https://lwn.net/Articles/1003421/ https://lwn.net/Articles/1003421/ Cyberax <div class="FormattedComment"> <span class="QuotedText">&gt; Normally, you would not use hard-linked operations in the newly cloned child context. If one of the setup operations fails, the entire chain fails</span><br> <p> How exactly is this going to be achieved for processes? As I understand, there's going to be a new visible intermediate state for the process, as the operations are being executed, unless the io_uring sequence locks the entire kernel.<br> <p> This also can cause a problem for userspace process migration. How do you interrupt the io_uring sequence to suspend it? After reading the patch series, I don't see how it would prevent long-running operations like read() from being introduced into the middle of the sequence.<br> <p> It really is a poorly-designed API. It is very much in line with the good old UNIX tradition of screwing up process management APIs.<br> </div> Tue, 24 Dec 2024 23:03:06 +0000 Chained operations in io_uring https://lwn.net/Articles/1003413/ https://lwn.net/Articles/1003413/ khim <font class="QuotedText">&gt; You are really determined to sink this patch series, I'm not sure why.</font> <p>I want to understand what that patch series hopes to achieve, mainly. This part is not reassuring: <i>Krisman hopes to be able to at least partially lift that constraint in the future</i>. And this is even more worrying: <i>The hope is to increase the set of possible operations over time, enabling the implementation of complex logic for the spawning of a new task.</i></p> <p>In essence we are supposed to accept some piece of the whole solution without us knowing where the whole thing even leads.</p> <p>And, worse yet, it's not clear what problem this whole thing is even supposed to solve!</p> <p>If it's safety of creation of a new process then it's one thing (there are no need for <code>io_uring</code>, we already have all the pieces), if it's 30% speedup for <code>posix_spawn</code>, then it's another thing.</p> <font class="QuotedText">&gt; But I see no reason to suspect some sort of evil plot here.</font> <p>Evil plot is unlikely. But it really looks like a solution in a search of a problem… and I want to see the problem and, more importantly, explanation why that's the best solution for it.</p> <p>As was noted in article one alternative solution would be to just create a dedicated system call that would include all the required operations. Or “double exec” if we just want to safely implement <code>posix_spawn</code>.</p> <p>And if it's “an attempt to see where it goes” then I don't really want to <b>sink</b> but more to “flesh it out”, understand how the full, final, solution would look like and, again, <b>who</b>, <b>why</b> and <b>how</b> would use it.</p> <p>Because as it stands currently, it's not clear to me what's the goal of all that activity – and that matters much more then minor details of the implementation in the current form.</p> <p>Even if we would achieve the final goal of being able to execute all <code>io_uring</code> commands in this sequence of these instructions between <code>clone</code> and <code>execute</code> why are we so sure it would be enough.</p> <p>Where do we plan to arrive with that change and what do we plan to achieve?</p> <font class="QuotedText">&gt; Hard links can be used in the execveat() sequence to implement a search path. In that case, continuing after failure is the desired outcome; you want to go until you find something you can actually execute.</font> <p>Ah. I see. While this, again, looks like a solution in a search of a problem (why to look up for the executable before executing it? what's the point of moving this pretty much optional functionality into the kernel? do we really want to try to continue after finding “kinda-sorta-suitable” binary that would end up being broken, for some reason?) at least now I understand what I didn't understood about that patch set.</p> <p>Thanks for explaining it: while I still am not sure how useful would it be to implement what it tries to implement (because, again, I couldn't see the end goal), at least <b>some</b> operations can be implemented in safe manner. That's better than how I understood it working. Thanks for explanation.</p> Tue, 24 Dec 2024 20:36:37 +0000 Chained operations in io_uring https://lwn.net/Articles/1003410/ https://lwn.net/Articles/1003410/ corbet You are really determined to sink this patch series, I'm not sure why. <p> Normally, you would not use hard-linked operations in the newly cloned child context. If one of the setup operations fails, the entire chain fails, with the status returned to the parent. No silently ignored failures. No unknown state. <p> Hard links can be used in the <tt>execveat()</tt> sequence to implement a search path. In that case, continuing after failure is the desired outcome; you want to go until you find something you can actually execute. <p> I am sorry if the article did not adequately convey that. <p> Doubtless there is interesting work to do to expand the range of actions available in the just-cloned child context; we will have to see what shape that takes. But I see no reason to suspect some sort of evil plot here. Tue, 24 Dec 2024 19:49:22 +0000 Chained operations in io_uring https://lwn.net/Articles/1003405/ https://lwn.net/Articles/1003405/ khim <font class="QuotedText">&gt; You can see that in this example:</font> <p>Which test is that? AFAICS <b>the only</b> test function that doesn't use linking, <i>test_unlinked_clone_sequence</i> just issues unlinked <code>IORING_OP_CLONE</code> and then expects this: <pre> if (cqe-&gt;res != -EINVAL) … Unlinked clone should have failed … </pre> <p>That's it. All other examples use linked operations, as they should.</p> <p>It's possible that I have misunderstood something, but at least at the first glance it's obvious <b>why</b> it have to be done that: after <code>IORING_OP_CLONE</code> is executed <b>the whole <code>io_uring</code> machinery</b> (I suspect 99% of Linux functionality) becomes, temporarily, “untouchable”… with some operations still permitted – only in linked form. And then it either succeeds (while silently ignoring errors leading to unknown state of the executed process) or fails – as whole.</p> <font class="QuotedText">&gt; So a program submits the chain of io_uring operations, and then it either succeeds (and a new process is created) or it fails, and the program that submitted it can choose how and whether to retry.</font> <p>Couldn't see that. At least in that patch series and set of examples.</p> <p>And it's obvious why: if you add that machinery then, suddenly, instead of simple and non-invasive patch that just adds couple of <code>io_uring</code> commands one would need to design completely new machinery which can support inter-process <code>io_uring</code> support! With execution happening in the context of one process and communication channel opened to another process.</p> <p>Sure, that's not impossible to create, but… do we really want to add so many new subtle features for 6% speedup?</p> <font class="QuotedText">&gt; So hard links aren't needed, and it's perfectly possible to write a correct program that closes important files with the current patch series.</font> <p>Show me the code. I couldn't find it. And I suspect that it's precisely as I have said: we only see 10% of the iceberg here, the majority of changes, 90% of iceberg is either doesn't exist or is not submitted for review.</p> Tue, 24 Dec 2024 19:37:53 +0000 Chained operations in io_uring https://lwn.net/Articles/1003400/ https://lwn.net/Articles/1003400/ daroc <div class="FormattedComment"> <span class="QuotedText">&gt; How would anything work without hard links? After IORING_OP_CLONE your process is in “undead” state. It's neither alive nor entirely dead, but the important part: it doesn't have any userspace that may act and do some decisions.</span><br> <p> It's possible that I've misunderstood how the patch series works, but I thought that if the whole series of operations fails, the program that originally started the operation is notified in the normal way (via an item in the io_uring completion queue). You can see that in this example: <a href="https://lwn.net/ml/all/20241209234421.4133054-3-krisman@suse.de/">https://lwn.net/ml/all/20241209234421.4133054-3-krisman@s...</a><br> <p> So a program submits the chain of io_uring operations, and then it either succeeds (and a new process is created) or it fails, and the program that submitted it can choose how and whether to retry. So hard links aren't needed, and it's perfectly possible to write a correct program that closes important files with the current patch series.<br> </div> Tue, 24 Dec 2024 19:12:47 +0000 Chained operations in io_uring https://lwn.net/Articles/1003384/ https://lwn.net/Articles/1003384/ khim <font class="QuotedText">&gt; In any case — nothing obliges developers to use hard links between <code>io_uring</code> operations.</font> <p>How would anything work without hard links? After <code>IORING_OP_CLONE</code> your process is in “undead” state. It's neither alive nor entirely dead, but the important part: it doesn't have <b>any userspace</b> that may act and do some decisions.</p> <p>That's <b>the whole point</b> of that patch series: to introduce a way to “clean up” that “undead” process by doing some operations when <b>userspace is entirely gone</b>.</p> <p>If you wouldn't use hard links… what is supposed to happen? How would non-hardlinked operations work without userspace? What is supposed to happen if some operation would would fail? We don't have any agent that may receive information about failure!</p> <p>Sure, we would know that combined operation would fail without running the program, but we would just make already stupid situation (where people try to execute programs with non-standard loader, get the message “file not found” and then spend hours trying to understand why program that's clearly there will all the proper permissions couldn't be execute) even worse.</p> <font class="QuotedText">&gt; While it is arguably suboptimal to introduce yet another API that must be used correctly or risk security problems, it is hardly the first such API in the kernel.</font> <p>It's worse than that: currently it's not “must be used correctly or risk security problems” but “it must be extended more before it would go beyond “proof of concept” phase, because in it's current form it's impossible to use it safely”.</p> <p><b>And we have no idea how much more should it be extended to become actually usable</b>. That's precisely why <a href="https://lwn.net/Articles/1003159/">I have said that we need to know</a> <b>who</b>, <b>why</b> and <b>how</b> plans to use that mechanism – because without such information we have no idea what needs to be added to it to make it actually usable.</p> <font class="QuotedText">&gt; Nothing prevents a poorly-written program from leaking various kinds of state to another program with the current fork()/exec() workflow.</font> <p>Sure, but correctly-written program can do everything <b>correctly</b>. And handle failures safely. Even if glibc fails to do that it's possible, <b>at least in theory</b>. That's <b>currently impossible to do with the new mechanism</b>.</p> <p>Can it be extended to handle these things? Sure: you can make it possible to receive information about <code>io_uring</code> operations in the parent process. Or introduce some high-level “cleanup” operations. Or do many other extra extensions… but before we would do all that the main question needs to be answered: what we are <b>actually</b> trying to do with that mechanism?</p> <font class="QuotedText">&gt; the best way to resolve that is usually with examples and more explanations.</font> <p>Sure, but why are you directing that request to me? The main advantage that was touted <a href="https://lwn.net/Articles/908268/">in the original work</a> way speed that's <i>6-10% faster than vfork() and 30+% faster than posix_spawn()</i>.</p> <p>I have no idea who would really need that speedup (most of the time time spent in <code>fork</code>/<code>exec</code> is minuscule compared to the time needed to run dynamic loader, verify signatures and so on), but that sounded somewhat sensible.</p> <p>But if all that complexity (including fight with <i>a kernel corruption after a few spawns</i>) and less reliability than what existing mechanisms provide is justified then it would be really nice to know who executes so many processes that they do care about <code>fork</code>/<code>exec</code> time, why the time needed to actually start process is not impeding their work and so on.</p> <p>Because if it's some silly specialized unikernel or some kind of cluster management software – then it may very well be handled better with a more focused, more specialized API instead of jenga tower that this patch series starts to build.</p> Tue, 24 Dec 2024 16:04:29 +0000 Chained operations in io_uring https://lwn.net/Articles/1003349/ https://lwn.net/Articles/1003349/ daroc <div class="FormattedComment"> Let's please not get too heated; even though we try to make articles as clear as possible, it's easy to have slightly different understandings of complex technical topics, and the best way to resolve that is usually with examples and more explanations.<br> <p> In any case — nothing obliges developers to use hard links between io_uring operations. If an important cleanup operation is necessary, and it is not safe to execute the new program if it fails, don't use a hard link. While it is arguably suboptimal to introduce yet another API that must be used correctly or risk security problems, it is hardly the first such API in the kernel. Nothing prevents a poorly-written program from leaking various kinds of state to another program with the current fork()/exec() workflow.<br> </div> Tue, 24 Dec 2024 14:41:15 +0000 Chained operations in io_uring https://lwn.net/Articles/1003343/ https://lwn.net/Articles/1003343/ khim <font class="QuotedText">&gt; The neat thing about the proposed <code>io_uring</code> solution is that the special properties of these chained operations already exist for other reasons</font> <p>Have you actually <b>read</b> the article? That one, specifically: <i> Krisman hopes to be able to at least partially lift that constraint in the future</i>.</p> <p>It's <b>extremely</b> clear to me that interface, as presented, it's not finished and not tested. Or, even worse, tested and is just feed to kernel developers in an insidious way to convince them to adopt huge hairball of API that would be immediately rejected if presented in it's full capacity… that's even worse then “unfinished and untested” API in my book (and I sincerely hope it's not that: <a href="https://en.wikipedia.org/wiki/Hanlon%27s_razor">Hazan's razor</a> and all that).</p> <font class="QuotedText">&gt; The only new things here are IORING_OP_CLONE that creates a new process (not able to run)</font> <p>Which is something that Linux haven't supported till today. Currently “load new executable in a process” is one atomic operation that starts from the state where kernel have something mapped and executable in it's address space and ends in the state where kernel have something mapped and executable in it's address space.</p> <p>An attempt to split that process in two looks innocuous enough, but it's entirely not clear what strange pitfalls it may hit.</p> <font class="QuotedText">&gt; The intent is that you can do something like write to a WAL, fsync the WAL if the WAL write succeeds, write to the final location if the WAL write and fsync succeed, and then regardless of success of the WAL and final location writes trigger a futex wake, all in a single submission to the kernel. </font> <p>Yes. And that's fine because code before and after <b>comes from the exact same codebase</b>. If some steps are omitted and/or failed then code that started the whole mess could, presumably, handle these failures gracefully.</p> <p>Compare with <code>io_uring</code> attempt to do <code>clone</code>/<code>exec</code> attempt: you are doing some important cleanup work after <code>clone</code> which is, well, <b>important</b> (or we wouldn't worry so much about doing it in the first place) – and <b>if it fails we execute foreign code, anyway</b>.</p> <p>This sounds, to me, like “hey, we have added nice security vulnerability to the kernel API, we just have no idea how to exploit it in the wild… contest is starting”!</p> <p>The most likely consequence would be pile of special cases forbid some “likely exploitable” instructions in the sequence between <code>IORING_OP_CLONE</code> and <code>IORING_OP_EXEC</code>.</p> <p>With ongoing maintenance when they would be discovered and open questions about what to do about apps that rely on these operations.</p> <p>Of course <code>safeexec</code> <b>also</b> includes all the same issues (and probably some more), but there's a big difference: because it's not a kernel API and it can be easily embedded into your application by linking it statically there are no need to support all the warts of the first version indefinitely. In can be tuned and fixed [relatively] freely without commitment to support it forever (because each released version is self-contained and would work like it did on the day one).</p> Tue, 24 Dec 2024 14:10:07 +0000 Chained operations in io_uring https://lwn.net/Articles/1003337/ https://lwn.net/Articles/1003337/ farnz <blockquote> special properties of these chained operations would have to stay in kernel forever. </blockquote> <p>The neat thing about the proposed <tt>io_uring</tt> solution is that the special properties of these chained operations already exist for other reasons - in order to allow you to queue up an I/O operation with an appropriate response on error, chains and hard links already exist<sup>[1]</sup>, and to allow <tt>io_uring</tt> to operate asynchronously to process context, it already knows how to handle trying to return to a userspace that isn't running. <p>The only new things here are <tt>IORING_OP_CLONE</tt> that creates a new process (not able to run) and <tt>IORING_OP_EXEC</tt> that replaces the program text and turns it into a ready-to-run process. Everything else already exists in <tt>io_uring</tt> for I/O purposes. <p><sup>[1]</sup> The intent is that you can do something like write to a WAL, <tt>fsync</tt> the WAL if the WAL write succeeds, write to the final location if the WAL write and <tt>fsync</tt> succeed, and then regardless of success of the WAL and final location writes trigger a futex wake, all in a single submission to the kernel. Tue, 24 Dec 2024 11:57:06 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003300/ https://lwn.net/Articles/1003300/ gutschke <div class="FormattedComment"> No writable/executable mapping is used in my proof of concept. Once the ephemeral ELF image has been exec()'d, there is only a single readable/executable mapping.<br> <p> I use a single mapping for both code and read-only data. That approach slightly simplified the already painfully complicated open-coded serialization of the various data structures that need to be passed into the child. But that could be split into two separate mappings for a production release. <br> <p> Or instead of passing data as part of the ELF image, all data could be passed into the ephemeral child over a pipe(). Those design details are certainly up for review.<br> <p> </div> Mon, 23 Dec 2024 17:00:31 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003299/ https://lwn.net/Articles/1003299/ khim <p>That can be solved if you would crate two mappings: read/write one and read/execute one. Or even just create read/write mapping, then fill it and then change to read/execute before <code>vfork</code>/<code>spawn</code>.</p> <p>These tricks are already used by JITs and most distributions, even very “enterprise” ones, have knobs to allow JITs, only iOS disabled that completely (and I don't think iOS is in scope for that project).</p> <p>Changing SELinux settings is needed in any solution, even if you introduce new syscall it's highly unlikely that SELinux wouldn't stop that till you retune it.</p> Mon, 23 Dec 2024 16:36:22 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003298/ https://lwn.net/Articles/1003298/ bluca <div class="FormattedComment"> <span class="QuotedText">&gt; It's “easier” in a sense that you can use it in applications for RHEL8+ and Android8+</span><br> <p> Actually I don't think you could use it in either, given it requires writable + executable memory, which is blocked by SELinux by default. Most sandboxing systems restrict that as well, as it's a very commonly used attack vector.<br> </div> Mon, 23 Dec 2024 16:29:48 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003245/ https://lwn.net/Articles/1003245/ khim <p>TL;DR version: this approach is better because in case of adoption failure (which is quite likely) one may just throw it away and forget about it, whileas if similar failure would happen with <code>io_uring</code> solution the code and special properties of these chained operations would have to stay in kernel forever.</p> <font class="QuotedText">&gt; I am still not 100% convinced that khim's solution is necessarily easier nor more robust.</font> <p>It's “easier” in a sense that you can use it in applications for RHEL8+ and Android8+ (and most other distributions also have kernels with <code>memfd_create</code>, too).</p> <p>That means that you could model your <code>io_uring</code> solution and test it on wide set of real-world tasks (since it can be used in production).</p> <p>Even if final solution would be to add either a dedicated syscall or set of <code>io_uring</code> operations (plus set of special “chaining” rules needed to make them usable) you would collect lots of data which would tell you what works and what doesn't work.</p> <p>If you start with addition to the kernel, on the other hand, then all these “large parent processes” deployed in various places wouldn't be able to use it for many years – and by the time when you would have real data collected from real apps… kernel API would be long-established and, most likely, not used (just like <code>posix_spawn</code> is barely used today).</p> <p>P.S. Of course if you plan to eventually go with <code>io_uring</code>, anyway, then it would be good idea to have API of your <code>safeexec</code> designed in way that would make it easy to switch to <code>io_uring</code>, at some point. Apps wouldn't even need to know that they stopped using “double exec” trick and switched to <code>io_uring</code> on kernels that have <code>io_uring</code> support, it would all be transparent for them.</p> Mon, 23 Dec 2024 14:39:37 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003236/ https://lwn.net/Articles/1003236/ fweimer <div class="FormattedComment"> You can use Solaris, which offers posix_spawn_file_actions_addclosefrom_np. It's always in glibc 2.34 or later, too. The other historically missing bits are posix_spawn_file_actions_addchdir_np and posix_spawn_file_actions_addfchdir_np (glibc 2.29 and later).<br> <p> In general, this is an anti-pattern, though, because we have to keep adding stuff that's easily expressed elsewhere in code. One issue is that one gets just one error code for an entirely list of actions, and that's bad from a debugging point of view. And one day, we'll need to wrap something where a first action produces a value needed by a second action, and we cannot easily force the value to a caller-supplied choice (like we do for file descriptors today).<br> <p> One silver lining is that vfork may not be as bad as we thought it was for a while. (The TCB sharing is empirically quite harmless for a wide variety of programs). Running compiled C code instead of walking an action list may be the better approach in the end.<br> </div> Mon, 23 Dec 2024 13:44:34 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003228/ https://lwn.net/Articles/1003228/ gutschke <div class="FormattedComment"> I am still not 100% convinced that khim's solution is necessarily easier nor more robust. But since I was curious whether the proposal to use existing kernel API's in somewhat unconventional ways would be viable at all, I wrote proof-of-concept code and uploaded it to: <a href="https://github.com/gutschke/safeexec/blob/main/safeexec.c">https://github.com/gutschke/safeexec/blob/main/safeexec.c</a><br> <p> Not surprisingly, since we are doing things that weren't quite intended to be done this way, there are warts and pit-falls. If my code was to be turned into a production-quality library, a good amount of additional polishing is necessary. But as is, this is evidence that khim's suggestion can address several of the concerns raised in these comments.<br> <p> (The best way to play with the code is to run it under the control of "strace". All it does is call "/bin/true" in a very round-about fashion.)<br> </div> Mon, 23 Dec 2024 09:26:25 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003196/ https://lwn.net/Articles/1003196/ ballombe <div class="FormattedComment"> <span class="QuotedText">&gt; I have no idea and as long as I have no idea I couldn't even say if that's a good idea or not!</span><br> <p> Well, I am glad to see that this does not impair your ability to write essay-sized posts about it.<br> </div> Sun, 22 Dec 2024 11:29:47 +0000 BPF! https://lwn.net/Articles/1003192/ https://lwn.net/Articles/1003192/ joib <div class="FormattedComment"> There was a nice article a few years ago that describes the problems with fork+exec, and indeed ends up recommending something like what you describe as a potential solution. <a href="https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf">https://www.microsoft.com/en-us/research/uploads/prod/201...</a><br> </div> Sun, 22 Dec 2024 07:30:16 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003183/ https://lwn.net/Articles/1003183/ khim <font class="QuotedText">&gt; But then why not just inline the stub into the "carefully written threadsafe library code" to avoid the double exec?</font> <p>Precisely because then your sign-verifying machinery couldn't verify your code. You are executing things in the context that's polluted by gigabytes of long-living code that may affect your carefully prepared binary.</p> <p>Even if it was sign-verified and correct when process was started it's not guaranteed to stay sign-verified and correct by the time you [try to] execute it.</p> <font class="QuotedText">&gt; Instead you would need to have a prebuilt helper binary (signed if necessary) that does the work based on some parameters.</font> <p>You could do what Turbo Pascal did decades ago: concatenate binary and parameters for said binary into one executable.</p> <p>So there would be signed part and unsigned parameters, signature can be easily checked when binary is loaded, even if it's loaded from <code>memfd</code>.</p> <font class="QuotedText">&gt; and then again, you can implement that approach without doing the double exec.</font> <p>It's possible in theory but it's not done today. And it doesn't eliminate issues of that code being corrupted and abused before new binary is spawn.</p> <p>And if we are not fighting <b>that</b> with <code>io_uring</code> proposal then I don't even have an idea what we are fighting for and against.</p> <p>The big problem of article that we are discussing here is that it carefully describes <b>the answer</b> to some issues, but it entirely neglects to list the issues that we are trying to fix!</p> <p>Not as bad as infamous “42” as the answer to “the ultimate question of life, the universe, and everything”, but very close to it: sure, that's a mechanism with a certain properties… but what it tries to do? What's the problem that couldn't be easily solved with it but is impossible to solve without it?</p> <p>I have no idea and as long as I have no idea I couldn't even say if that's a good idea or not!</p> <p>That's why I'm talking about “buzzword compliance”: simply because if “hey, it's <code>io_uring</code>, <i>new</i> and <i>shiny</i>” is not the goal then what <b>is</b> the goal? Where does that solution is supposed to send us? And why couldn't we arrive there via simpler means?</p> Sat, 21 Dec 2024 20:09:41 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003178/ https://lwn.net/Articles/1003178/ roc <div class="FormattedComment"> Writing arbitrary code into a memfd and then exec'ing it would get around security subsystems that try to prevent running unsigned/unvalidated binaries. So that's not a general-enough solution. Instead you would need to have a prebuilt helper binary (signed if necessary) that does the work based on some parameters. But then why not just inline the stub into the "carefully written threadsafe library code" to avoid the double exec? And that's basically posix_spawn() today.<br> <p> You might say that the "users write arbitrary code into a memfd" part is essential for flexibility. Even if we ignore the security issues, it would be nasty to program directly. People would inevitably wrap it in some kind of tiny, portable virtual machine for users to express their setup code ... and then again, you can implement that approach without doing the double exec.<br> </div> Sat, 21 Dec 2024 19:31:12 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003179/ https://lwn.net/Articles/1003179/ quotemstr <div class="FormattedComment"> <span class="QuotedText">&gt; We don't have a full set of system calls for remotely doing everything that a process can do by itself.</span><br> <p> In a world with a more regular system interface, *every* system call would require callers specify all object on which to operate, explicitly. We wouldn't have operations that work on "the current process" or "the current thread". In this world, the process bootstrapping the GP proposes would fall naturally out of the general shape of the API surface.<br> </div> Sat, 21 Dec 2024 19:30:16 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003168/ https://lwn.net/Articles/1003168/ ma4ris8 <div class="FormattedComment"> Let's have a threaded program. It opens and closes file descriptors. Some of those have FD_CLOSE.<br> Task is to create a child program. Child program will have three file descriptors, parent's three fds<br> mapped as child's stdin, stdout and stderr. Close all unrelated file descriptors.<br> Perhaps Valgrind's file descriptors with fds near upper bound, 1024, are also allowed to pass thru.<br> <p> One way is to fork, then open /proc/self/fd, close unrelated fds, remap related ones into 0,1 and 2.<br> After that, then exec the final child with a clean state. If parent has large memory foot print, this is heavy.<br> <p> The other way is to do posix_spawn(). Spawn intermediate process, which closes unrelated fds, remaps related<br> ones into 0,1 and 2. After clean up, execute the final child process. Drawback is to have the middle process<br> to do the clean up, but if parent has large memory foot print, this is light, compared to fork.<br> <p> Third way: how to do it so, that the cleanups could be done in an elegant and memory safe way,<br> without the separate middle process?<br> <p> <p> <p> </div> Sat, 21 Dec 2024 17:26:49 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003163/ https://lwn.net/Articles/1003163/ corbet "Buzzword compliance" takes the work of people who are trying to improve the system and casts it as something useless. If it were my work, I would find that insulting. I do not believe that the people working on this are concerned about buzzwords, they are trying to solve real problems. Please try being a bit more respectful toward them. Sat, 21 Dec 2024 16:48:03 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003159/ https://lwn.net/Articles/1003159/ khim <font class="QuotedText">&gt; But please stop insulting the work of others, that does not help anybody.</font> <p>Where do you see insults? I've faced the need to mangle simple and easy to understand and implement ideas into pretzels to include all the right buzzwords at my $DAYJOBs often enough that I can easily see buzzword compliance as explicit, or more likely, implicit part of the requirements.</p> <p>And very often it's even the most important one: if you couldn't cause enough buzz around your idea then it would die (except if there are some <b>concrete</b> tasks for <b>concrete</b> customers that may need it) even if it's pretty good, but with enough buzz around your idea you may push it even if it's totally stupid and would hurt everyone in the long run.</p> <font class="QuotedText">&gt; Khim, if you have a better idea, please submit a patch showing it.</font> <p>There are no patch because in-kernel parts are already done… years ago, in fact.</p> <p>And to discuss userspace part we need some idea about <b>who</b>, <b>why</b> and <b>how</b> plans to use that mechanism.</p> <p>The list of interested parties is not in the article thus it's hard for me to offer anything concrete because it's not clear to me how much flexibility is needed or wanted.</p> <p>Implementation of <code>posix_spawn</code> is doable but would be significant amount of work without any clear benefits: do we have lots of users of that syscall? If yes, then where are they, if not then why are they so rare?</p> <p>IOW: I don't see enough of a picture related to that work to judge it fairly and if “buzzword-compliance” <b>is</b> part of reasoning (even if an implicit one) then it could be that <code>io_uring</code>-based solution <b>is</b> the best way forward. <b>Especially</b> if it's a solution-in-a-search-of-a-problem: it's much easier to make someone excited about <code>io_uring</code> solution than about solution that just combines well-known syscalls in a way that makes <code>posix_spawn</code> safer. </p> Sat, 21 Dec 2024 16:44:02 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003158/ https://lwn.net/Articles/1003158/ corbet Khim, if you have a better idea, please submit a patch showing it. But please stop insulting the work of others, that does not help anybody. Sat, 21 Dec 2024 16:10:33 +0000 Why not just have a one-step spawn? https://lwn.net/Articles/1003156/ https://lwn.net/Articles/1003156/ khim <p>A much simpler approach would be to just add some code that would do that setup in the empty process. And we already have <a href="https://man7.org/linux/man-pages/man2/memfd_create.2.html">memfd_create</a>/<a href="https://man7.org/linux/man-pages/man2/execveat.2.html">execveat</a> combo that can do that.</p> <p>If you want – add flag to the <code>clone</code> that would call <a href="https://man7.org/linux/man-pages/man2/execveat.2.html">execveat</a>. And then new code in an entirely empty image can do whatever it needs to prepare for the execution of the <b>real</b> binary that you want to execution.<p> <p>Why shove <code>io_uring</code> into something that <b>already</b> can be done entirely from userspace? Buzzword compliance?</p> Sat, 21 Dec 2024 15:39:47 +0000