zlacker

[parent] [thread] 12 comments
1. sylwar+(OP)[view] [source] 2022-09-10 13:33:57
Richard Stallman POSIX now will have to do one of the hardest stuff in the software realm: resist planned obsolescence while replacing some interfaces with "in-the-end better" interfaces, that on the long run. This is extremely though: often, replacing "in-the-end better" interfaces is actually planned obsolescence.

For instance event based programming, "epoll" should replace "select". Synchronous programming, signalfd, timerfd, etc... but then "signals" should be more accurately classified, for instance in monothreaded applications segfault won't be delivered from a "signalfd" in a "epoll" syscall... and why not keep only the "realtime signal" behavior?

If POSIX only "add" new "options", in the end, you'll get gigantic spec just being the mirror image of the very few implementations since its size will make it unreasonable for a new implementation from scratch.

The "leaner POSIX" route seems the right way here, but it is really easier to say than to do.

replies(1): >>bitwiz+3k
2. bitwiz+3k[view] [source] 2022-09-10 15:51:42
>>sylwar+(OP)
No, I/O completion ports (which Linux only recently got a form of) should replace epoll and select.

fork() should be ditched.

replies(1): >>thetea+LV
◧◩
3. thetea+LV[view] [source] [discussion] 2022-09-10 19:49:07
>>bitwiz+3k
> fork() should be ditched.

Why? In favor of what?

replies(3): >>10000t+gZ >>aseipp+F01 >>bitwiz+e71
◧◩◪
4. 10000t+gZ[view] [source] [discussion] 2022-09-10 20:18:25
>>thetea+LV
Embryonic processes. Basically:

1. Call a function that creates an empty child process in a suspended state.

2. Call functions that: map/write memory into the child process’s address space; add file descriptors to the child process; set the child process’s initial register values; and so on.

3. Call a function to unsuspend and run the child process.

replies(1): >>lgg+u51
◧◩◪
5. aseipp+F01[view] [source] [discussion] 2022-09-10 20:32:08
>>thetea+LV
Because fork() was very simple and conceptually "easy" to do when it first was introduced, and is now massively complex and has huge implications on every part of the system. It's not compositional, isn't thread safe, insecure by default (inherits env/fds), and it's also slow with all the state it must copy. And at a conceptual level it doesn't work in environments where the nature of a "process" and "address space" aren't synonymous. For instance if your application uses a hardware accelerator (NIC, GPU, whatever) fork() isn't ever viable or sensible, since the hardware resources can't be duplicated safely. And it'll never work in a design like WebAssembly (just an example of this idea, but WASM isn't the only one), again "process" and "virtual memory address space" are not the same. Consider that posix_spawn can make reasonable sense in WebAssembly at a first guess ("launch this wasm module"), but fork() in contrast is much more difficult when it implies COW semantics.

The reality is fork() is pretty much exclusively used to launch new processes these days, outside a few specific cases. Today, it's a poor fit for that problem. And the answer is what Windows has been doing (and POSIX has now had) for a long time: explicitly launching processes by giving a handle/pathname to an executable like posix_spawn. That's the first solution, anyway; a better one would be more capability-oriented design where you have to supply a new address space with all its resources yourself.

This HotOS paper is a pretty good detailed coverage of the argument; I find it very convincing. If fork() went away, I honestly wouldn't miss it, I think. https://www.microsoft.com/en-us/research/uploads/prod/2019/0...

replies(1): >>thetea+wq1
◧◩◪◨
6. lgg+u51[view] [source] [discussion] 2022-09-10 21:19:35
>>10000t+gZ
fork() is terrible, but embryonic processes also have a lot of performance issues and prevent a number of valuable security mitigations. In general a spawn() style mechanism seems like a better approach (despite the deficiencies of specific examples like posix_spawn()).
replies(1): >>matu3b+1k1
◧◩◪
7. bitwiz+e71[view] [source] [discussion] 2022-09-10 21:36:38
>>thetea+LV
Something like windows CreateProcess or posix_spawn().

fork() plays havoc with threads. If you want to start a new process, specify a fresh process image.

replies(2): >>matu3b+Gj1 >>sylwar+mq1
◧◩◪◨
8. matu3b+Gj1[view] [source] [discussion] 2022-09-10 23:52:35
>>bitwiz+e71
so does posix_spawn, since you can leak file descriptors into parallel spawning threads/processes before the execve (where they are closed if having O_CLEXEC).
replies(1): >>lgg+Gs1
◧◩◪◨⬒
9. matu3b+1k1[view] [source] [discussion] 2022-09-10 23:55:47
>>lgg+u51
What is better in an unfixable race condition (the time before the execve where stuff leaks)?
replies(1): >>lgg+zs1
◧◩◪◨
10. sylwar+mq1[view] [source] [discussion] 2022-09-11 01:05:46
>>bitwiz+e71
linux clone?
◧◩◪◨
11. thetea+wq1[view] [source] [discussion] 2022-09-11 01:08:41
>>aseipp+F01
> It's not compositional

What does that mean?

> isn't thread safe

True but it's not meant to be. I'm not sure there are many if any valid use cases for forking and not immediately exec-ing and using threads together in the same application.

> insecure by default (inherits env/fds)

Inherits env and open file descriptors by design. It's pretty much always been understood that if you fork in most scenarios you immediately exec. You can set file to close on exec and set a new env if desired, and not do that if it's not.

> and it's also slow with all the state it must copy.

I thought it was mostly COW?

> And at a conceptual level it doesn't work in environments where the nature of a "process" and "address space" aren't synonymous.

Yeah valid argument. posix_spawn man page says:

> "The posix_spawn() and posix_spawnp() functions are used to create a new child process that executes a specified file. These functions were specified by POSIX to provide a standardized method of creating new processes on machines that lack the capability to support the fork(2) system call. These machines are generally small, embedded systems lacking MMU support.".

posix_spawn is POSIX and has existed along side fork since POSIX.2001. So your saying you want every application ever written to be automatically portable to systems the can't support fork, therefore get rid of fork entirely? I guess.

◧◩◪◨⬒⬓
12. lgg+zs1[view] [source] [discussion] 2022-09-11 01:34:41
>>matu3b+1k1
I think it goes without saying that since I stated fork() is terrible that I am not advocating for spawning new processes via any of the variants of exec() since they all depend on fork(). I directly stated posix_spawn() was the right technique (despite its deficiencies and a somewhat terrible interface).

My point was that embryonic processes aren't really the right solution since they require exposing a whole bunch to powerful primitives like read/write of remote address spaces in order to spawn processes. That ends up being slow (because each one of those calls is necessarily a syscall and requires manipulating page tables to mess with the other process). It also means to prevent abuse you need to be very carefully control when to revoke those privileges.

The obvious solution is to take all the operations you would have done to the remote process, encode them in some form, and pass them off to a secure agent (in this case the kernel) that can do them in bulk. That solves both perf issues (1 syscall, and no repeated round tripping through multiple address spaces), and the security issue (you no longer need to expose primitives to manipulate the remote process to every process that is allowed to spawn a new one).

◧◩◪◨⬒
13. lgg+Gs1[view] [source] [discussion] 2022-09-11 01:36:15
>>matu3b+Gj1
Yeah, that is a problem, and it is totally fixable. Checkout `POSIX_SPAWN_CLOEXEC_DEFAULT` on macOS for an example. Again, just because there are API deficiencies doesn't mean that the idea is wrong.
[go to top]