Hacker News new | ask | show | jobs
by disjointrevelry 4203 days ago
Reminds me of debian and ubuntu's requirement that apt-get is run under root. There is simple ways to get apt-get to run on non-root, but it require giving permission to non-root account to modify important package signature files. But, they're not as bad as docker. It's becoming norm for these US/Silicon companies to give very bad integrity on data.
1 comments

The difference here is that apt-get and its ilk need to modify critical system state basically every time they run, and that state isn't controlled by a persistent daemon. I actually consider this a great tradeoff: yes I have to use sudo to run that one command, but I don't have a long-lived process sitting around pulling data down off the Internet and doing stuff with it while humming along as uid 0.

It's also literally one line of code in most UNIX-based languages (syscall.Setuid(<uid that isn't root>) in Go, FWIW) to drop root privileges before doing something unsafe. Even if the main Docker daemon absolutely has to run as root most of the time, it can and should fork and drop that access for anything dealing with moving data between unstrusted (e.g. the Internet, user input, etc.) and trusted (verified, read-only local state) security domains.

As a quick word of caution (which doesn't invalidate anything you've said), Go has a long-standing bug[0] whereby syscall.Setuid doesn't always apply to all threads (on Linux at least) so extra care does have to be taken.

[0] https://github.com/golang/go/issues/1435

Wow. That's a fun one.

It's also a perfect example of why even really amazing teams reinventing a language/tooling ecosystem from scratch stumble over problems that were solved years (or even decades ago) in preceding platforms. I leave it as an exercise for the reader to decide if the "reinvent from scratch" critique is more deserved by Docker, Go, or Linux.

That being said, I'm pretty sure even the broken Setuid behavior described there would be good enough to sandbox a thread or child proc that was just handling buffered I/O into and out of the xz binary.

It's not a bug, strictly speaking; it's just a feature that's really easy to misunderstand. The `syscall` package in Go tends to be logic-less wrappers around the raw syscalls, and that's what happens here.

Linux actually maintains the uid/euid/suid/gid/egid/sgid/etc. fields per OS thread (which are actually processes, just with a bit of shared memory). The raw syscalls only change the fields on a single task.

Glibc is where the logic happens to propagate that setting to all threads, by setting up signal handlers and immediately triggering a signal, IIRC.

You can get the useful behaviour by using cgo and importing setresuid from unistd.h.

What go should probably do here is:

1) Add os.Set{res,re,}{u,g}id, which implements the logic from glibc.

2) Remove syscall.Set{res,re,}{u,g}id. Anyone that wants that behaviour can use syscall.Syscall6 and syscall.SYS_SETUID or whatever. At least, they could add some really loud godoc to those methods.

The main problem with that is that `os` tries to have a cross-platform API, and (for example) BSD has no saved user or group IDs and therefore no setres{u,g}id. I suspect Windows and Plan9 are even weirder.

> It's not a bug, strictly speaking; it's just a feature that's really easy to misunderstand.

It is a bug on Linux -- strictly speaking. Did you see how it is patched? They removed setuid from linux: https://codereview.appspot.com/106170043

> "That these functions should made to fail rather than succeed in their broken state."

Wow, nice bug.

I like a lot of things about Go, but I wish they didn't invent their own mini operating system in the runtime on top of Linux/POSIX. This bug seems like a good example of why that's a leaky abstraction.

I think it would be interesting to explore a coroutines + threads + channels design space for a concurrent language. Basically like Go, except without the green thread implementation.

For CPU bound work, you can use threads. For I/O bound work, use coroutines. And then they all compose together with channels. The implementation would be a lot simpler because it works with the OS rather than trying to paper over it.