Closed gopherbot closed 4 years ago
Well, you just must just put the gorutine creation after the suid/sguid call. Isn't that what you would actually expect? ... en = syscall.Setuid(uid) if en != 0 { fmt.Println("Setuid error:", os.Errno(en)) os.Exit(1) } for ii := 1; ii < 10; ii++ { go printIds(ii) time.Sleep(1e8) } printIds(0) sudo -i export GOMAXPROCS=2 /tmp/setuid 65534 65534 gorutine 1: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 2: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 3: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 4: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 5: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 6: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 7: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 8: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 9: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 0: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 1: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 2: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 3: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 4: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 5: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 6: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 7: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 8: uid=65534 euid=65534 gid=65534 egid=65534
A more concrete example of this is perhaps: http://golang.org/src/pkg/time/tick.go To be safe, one would have to check the code of every package you import to be sure that you don't inadvertently call a function that starts a goroutine before you get round to calling Setuid.
Example code which shows how I discovered this problem: package main import ( "os" "net" "http" "fmt" "syscall" "log" ) type Handler string func (hh *Handler) ServeHTTP(con http.ResponseWriter, req *http.Request) { fmt.Fprint(con, "Hello! I am %s. My UID/GID is %d/%d. Bye!\n", *hh, syscall.Getuid(), syscall.Getgid()) } func main() { // To listen on port 80 we need root privileges ls, err := net.Listen("tcp", "127.0.0.1:80") if err != nil { log.Exitln("Can't listen:", err) } // We don't need root privileges any more if en := syscall.Setgid(65534); en != 0 { log.Exitln("Setgid error:", os.Errno(en)) } if en := syscall.Setuid(65534); en != 0 { log.Exitln("Setuid error:", os.Errno(en)) } // Run http service without root privileges handler := Handler("Test handler") if err = http.Serve(ls, &handler); err != nil { log.Exitln("Http server:", err) } } Compile it and run as root. Then use ps -efL to show threads: UID PID PPID LWP C NLWP STIME TTY TIME CMD nobody 32401 32333 32401 0 2 18:48 pts/2 00:00:00 ./http root 32401 32333 32402 0 2 18:48 pts/2 00:00:00 ./http There is two threads. But I didn't create any gorutine explicitly. Use curl to send request to the application: $ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/65534. Bye! Now ps shows: UID PID PPID LWP C NLWP STIME TTY TIME CMD nobody 32401 32333 32401 0 3 18:48 pts/2 00:00:00 ./http root 32401 32333 32402 0 3 18:48 pts/2 00:00:00 ./http nobody 32401 32333 32406 0 3 18:49 pts/2 00:00:00 ./http Use siege stress test: $ siege 127.0.0.1 -c25 -d0 -t 10s ** SIEGE 2.70 ** Preparing 25 concurrent users for battle. The server is now under siege... Lifting the server siege.. done. Transactions: 12543 hits Availability: 100.00 % Elapsed time: 9.99 secs Data transferred: 0.65 MB Response time: 0.02 secs Transaction rate: 1255.56 trans/sec Throughput: 0.06 MB/sec Concurrency: 24.79 Successful transactions: 12544 Failed transactions: 0 Longest transaction: 0.04 Shortest transaction: 0.01 Now ps shows: UID PID PPID LWP C NLWP STIME TTY TIME CMD nobody 32401 32333 32401 0 11 18:48 pts/2 00:00:01 ./http root 32401 32333 32402 0 11 18:48 pts/2 00:00:01 ./http nobody 32401 32333 32406 0 11 18:49 pts/2 00:00:01 ./http nobody 32401 32333 32553 0 11 18:51 pts/2 00:00:00 ./http nobody 32401 32333 32554 2 11 18:51 pts/2 00:00:01 ./http root 32401 32333 32555 3 11 18:51 pts/2 00:00:01 ./http root 32401 32333 32556 3 11 18:51 pts/2 00:00:01 ./http root 32401 32333 32557 0 11 18:51 pts/2 00:00:00 ./http root 32401 32333 32558 3 11 18:51 pts/2 00:00:01 ./http nobody 32401 32333 32559 3 11 18:51 pts/2 00:00:01 ./http nobody 32401 32333 32560 3 11 18:51 pts/2 00:00:01 ./http Use curl a few times: $ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/0. Bye! $ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/0. Bye! $ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/0. Bye! It's nice! I talking with root on your server!
Oh, you are right. There is currently no official way to use a net socket without spawning 2nd goroutine (EpollWait): goroutine 2 [3]: runtime.entersyscall+0x28 /data4/soft/go/go/src/pkg/runtime/proc.c:577 runtime.entersyscall() syscall.Syscall6+0x5 /data4/soft/go/go/src/pkg/syscall/asm_linux_amd64.s:40 syscall.Syscall6() syscall.EpollWait+0x8d /data4/soft/go/go/src/pkg/syscall/zsyscall_linux_amd64.go:188 syscall.EpollWait(0x7fda00000006, 0x7fda62d566a0, 0x100000001, 0xffffffff, 0xc, ...) net.*pollster·WaitFD+0xfe /data4/soft/go/go/src/pkg/net/fd_linux.go:116 net.*pollster·WaitFD(0x7fda62d564d0, 0x0, 0x0, 0x0, 0x0, ...) net.*pollServer·Run+0xa3 /data4/soft/go/go/src/pkg/net/fd.go:207 net.*pollServer·Run(0x7fda62d20600, 0x0) runtime.goexit /data4/soft/go/go/src/pkg/runtime/proc.c:149 runtime.goexit() but opening a file descriptor works: f, err := os.Open("/etc/shadow", os.O_RDONLY | os.O_SYNC , 0666) fmt.Println(runtime.Goroutines()) ... syscall.Setuid(uid) ... syscall.Setgid(gid) var buf [20]byte _, err = f.Read(buf[:]); fmt.Println(buf) time.Sleep(100e9) ./run open /etc/shadow: permission denied sudo ./run 1 [114 111 111 ... ps -efL|grep run mc 14097 24745 14097 0 1 19:36 pts/4 00:00:00 /run 1001 1001
The syscall is doing what it advertises: it invokes the Linux system call. And the Linux system call only affects the calling thread (!), confirmed by reading the sources. I was surprised to find that setuid works when called from a C Linux pthreads (NPTL) program, though. So I investigated further. It turns out that glibc's setuid sends a signal to every other thread to cause them to invoke the system call too. http://goo.gl/8zf3C - called by setuid http://goo.gl/CcXyX - signal handler I suppose Go is going to need to do this at some point, as part of implementing os.Setuid, os.Setgid, etc. What a crock. For now you can work around this by calling runtime.LockOSThread. That locks the goroutine onto its current OS thread, so that it only runs in that thread and that thread only runs that goroutine. Then you can call Setuid Setgid etc and also ForkExec.
Status changed to LongTerm.
If I add runtime.LockOSThread in my second example, just before Setgid, I get strange behavior: michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/0. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/65534. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/0. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/65534. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/65534. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/0. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/65534. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/0. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 65534/65534. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/0. Bye! michal@md-lap:~$ curl 127.0.0.1 Hello! I am Test handler. My UID/GID is 0/65534. Bye!
Attachments:
I probably found workaround for my web application: package main import ( "os" "net" "http" "fmt" "syscall" "runtime" "log" ) func lockUidGid(new_uid, new_gid int) { runtime.LockOSThread() uid := syscall.Getuid() gid := syscall.Getgid() if uid == new_uid && gid == new_gid { return } if en := syscall.Setgid(new_uid); en != 0 { log.Exitln("Setgid error:", os.Errno(en)) } if en := syscall.Setuid(new_gid); en != 0 { log.Exitln("Setuid error:", os.Errno(en)) } } type Handler string func (hh *Handler) ServeHTTP(con http.ResponseWriter, req *http.Request) { lockUidGid(65534, 65534) fmt.Fprintf(con, "Hello! I am %s. My UID/GID is %d/%d. Bye!\n", *hh, syscall.Getuid(), syscall.Getgid()) } func main() { // To listen on port 80 we need root privileges ls, err := net.Listen("tcp", "127.0.0.1:80") if err != nil { log.Exitln("Can't listen:", err) } // We don't need root privileges any more lockUidGid(65534, 65534) // Run http service without root privileges handler := Handler("Test handler") if err = http.Serve(ls, &handler); err != nil { log.Exitln("Http server:", err) } } After running siege there is no threads with root privileges: $ siege 127.0.0.1 -c25 -d0 -t10s ** SIEGE 2.69 ** Preparing 25 concurrent users for battle. The server is now under siege... Lifting the server siege.. done. Transactions: 28782 hits Availability: 100.00 % Elapsed time: 9.79 secs Data transferred: 1.59 MB Response time: 0.01 secs Transaction rate: 2939.94 trans/sec Throughput: 0.16 MB/sec Concurrency: 24.86 Successful transactions: 28782 Failed transactions: 0 Longest transaction: 0.04 Shortest transaction: 0.00 $ ps -efL|egrep 'http|UID'|egrep -v egrep UID PID PPID LWP C NLWP STIME TTY TIME CMD nobody 4109 2928 4109 1 10 23:00 pts/1 00:00:04 ./http nobody 4109 2928 4110 0 10 23:00 pts/1 00:00:02 ./http nobody 4109 2928 4112 0 10 23:00 pts/1 00:00:02 ./http nobody 4109 2928 4153 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4154 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4155 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4156 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4157 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4158 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4159 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4109 1 10 23:00 pts/1 00:00:04 ./http nobody 4109 2928 4110 0 10 23:00 pts/1 00:00:02 ./http nobody 4109 2928 4112 0 10 23:00 pts/1 00:00:02 ./http nobody 4109 2928 4153 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4154 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4155 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4156 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4157 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4158 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4159 0 10 23:01 pts/1 00:00:01 ./http Thanks!
I probably found workaround for my web application: package main import ( "os" "net" "http" "fmt" "syscall" "runtime" "log" ) func lockUidGid(new_uid, new_gid int) { runtime.LockOSThread() uid := syscall.Getuid() gid := syscall.Getgid() if uid == new_uid && gid == new_gid { return } if en := syscall.Setgid(new_uid); en != 0 { log.Exitln("Setgid error:", os.Errno(en)) } if en := syscall.Setuid(new_gid); en != 0 { log.Exitln("Setuid error:", os.Errno(en)) } } type Handler string func (hh *Handler) ServeHTTP(con http.ResponseWriter, req *http.Request) { lockUidGid(65534, 65534) fmt.Fprintf(con, "Hello! I am %s. My UID/GID is %d/%d. Bye!\n", *hh, syscall.Getuid(), syscall.Getgid()) } func main() { // To listen on port 80 we need root privileges ls, err := net.Listen("tcp", "127.0.0.1:80") if err != nil { log.Exitln("Can't listen:", err) } // We don't need root privileges any more lockUidGid(65534, 65534) // Run http service without root privileges handler := Handler("Test handler") if err = http.Serve(ls, &handler); err != nil { log.Exitln("Http server:", err) } } After running siege there is no threads with root privileges: $ siege 127.0.0.1 -c25 -d0 -t10s ** SIEGE 2.69 ** Preparing 25 concurrent users for battle. The server is now under siege... Lifting the server siege.. done. Transactions: 28782 hits Availability: 100.00 % Elapsed time: 9.79 secs Data transferred: 1.59 MB Response time: 0.01 secs Transaction rate: 2939.94 trans/sec Throughput: 0.16 MB/sec Concurrency: 24.86 Successful transactions: 28782 Failed transactions: 0 Longest transaction: 0.04 Shortest transaction: 0.00 $ ps -efL|egrep 'http|UID'|egrep -v egrep UID PID PPID LWP C NLWP STIME TTY TIME CMD nobody 4109 2928 4109 1 10 23:00 pts/1 00:00:04 ./http nobody 4109 2928 4110 0 10 23:00 pts/1 00:00:02 ./http nobody 4109 2928 4112 0 10 23:00 pts/1 00:00:02 ./http nobody 4109 2928 4153 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4154 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4155 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4156 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4157 0 10 23:01 pts/1 00:00:00 ./http nobody 4109 2928 4158 0 10 23:01 pts/1 00:00:01 ./http nobody 4109 2928 4159 0 10 23:01 pts/1 00:00:01 ./http Thanks!
It's still not guaranteed that future goroutines won't have the original 0/0 uid/gid. Obviously if all tasks have been switched then you're safe but there's no guarantee that will switch all the tasks. Using the network capability is much safer if you are worried about this kind of thing. Russ
Comment 15 by m@capitanio.org:
>For now you can work around this by calling runtime.LockOSThread. runtime.GOMAXPROCS(1) runtime.LockOSThread() ls, _ := net.Listen("tcp", "localhost:42") syscall.Setuid(uid) http.Serve(ls, nil) Was that what you meant? It's actually impossible ;) There is always +1 thread spawned by Listen that keeps running as root. ps -efL| grep setuid mc 19213 24745 19213 0 2 22:41 pts/4 00:00:00 /tmp/setuid 1001 1001 root 19213 24745 19215 0 2 22:41 pts/4 00:00:00 /tmp/setuid 1001 1001 ^\SIGQUIT: quit PC=0x411b25 runtime.futex+0x23 /data4/soft/go/go/src/pkg/runtime/linux/amd64/sys.s:137 runtime.futex() futexsleep+0x50 /data4/soft/go/go/src/pkg/runtime/linux/thread.c:51 futexsleep(0x664c18, 0x300000003, 0x0, 0x0) futexlock+0x85 /data4/soft/go/go/src/pkg/runtime/linux/thread.c:119 futexlock(0x664c18, 0x100000000) runtime.notesleep+0x25 /data4/soft/go/go/src/pkg/runtime/linux/thread.c:204 runtime.notesleep(0x664c18, 0x7fffa0812498) nextgandunlock+0x146 /data4/soft/go/go/src/pkg/runtime/proc.c:343 nextgandunlock() scheduler+0x16f /data4/soft/go/go/src/pkg/runtime/proc.c:536 scheduler() runtime.mstart+0x74 /data4/soft/go/go/src/pkg/runtime/proc.c:393 runtime.mstart() _rt0_amd64+0x95 /data4/soft/go/go/src/pkg/runtime/amd64/asm.s:69 _rt0_amd64() goroutine 2 [3]: runtime.entersyscall+0x28 /data4/soft/go/go/src/pkg/runtime/proc.c:577 runtime.entersyscall() syscall.Syscall6+0x5 /data4/soft/go/go/src/pkg/syscall/asm_linux_amd64.s:40 syscall.Syscall6() syscall.EpollWait+0x8d /data4/soft/go/go/src/pkg/syscall/zsyscall_linux_amd64.go:188 syscall.EpollWait(0x7f6600000006, 0x7f669eb58950, 0x100000001, 0xffffffff, 0xc, ...) net.*pollster·WaitFD+0xfe /data4/soft/go/go/src/pkg/net/fd_linux.go:116 net.*pollster·WaitFD(0x7f669eb587a0, 0x0, 0x6400000000, 0x0, 0x0, ...) net.*pollServer·Run+0xa3 /data4/soft/go/go/src/pkg/net/fd.go:207 net.*pollServer·Run(0x7f669eb27600, 0x0) runtime.goexit /data4/soft/go/go/src/pkg/runtime/proc.c:149 runtime.goexit() goroutine 1 [4]: runtime.gosched+0x77 /data4/soft/go/go/src/pkg/runtime/proc.c:558 runtime.gosched() runtime.chanrecv+0x18b /data4/soft/go/go/src/pkg/runtime/chan.c:364 runtime.chanrecv(0x7f669eb3eea0, 0x7f669eb13da8, 0x0, 0x0, 0x0, ...) runtime.chanrecv1+0x41 /data4/soft/go/go/src/pkg/runtime/chan.c:444 runtime.chanrecv1(0x7f669eb3eea0, 0x7f669eb123c0) net.*pollServer·WaitRead+0x52 /data4/soft/go/go/src/pkg/net/fd.go:247 net.*pollServer·WaitRead(0x7f669eb27600, 0x7f669eb123c0, 0x0, 0x0) net.*netFD·accept+0x39a /data4/soft/go/go/src/pkg/net/fd.go:579 net.*netFD·accept(0x7f669eb123c0, 0x43efe8, 0x0, 0x0, 0x0, ...) net.*TCPListener·AcceptTCP+0x71 /data4/soft/go/go/src/pkg/net/tcpsock.go:261 net.*TCPListener·AcceptTCP(0x7f669eb0f178, 0x7f669eb13ee0, 0x0, 0x0, 0x1007f6600000001, ...) net.*TCPListener·Accept+0x49 /data4/soft/go/go/src/pkg/net/tcpsock.go:271 net.*TCPListener·Accept(0x7f669eb0f178, 0x0, 0x0, 0x0, 0x0, ...) http.Serve+0x7a /data4/soft/go/go/src/pkg/http/server.go:665 http.Serve(0x7f669eb27b40, 0x7f669eb0f178, 0x7f669eb4b030, 0x7f669eb0f0a8, 0x0, ...) main.main+0x886 /home/mc/server/go/tests/setuid2.go:73 main.main() runtime.mainstart+0xf /data4/soft/go/go/src/pkg/runtime/amd64/asm.s:77 runtime.mainstart() runtime.goexit /data4/soft/go/go/src/pkg/runtime/proc.c:149 runtime.goexit() rax 0xfffffffffffffffc rbx 0x664c18 rcx 0xffffffffffffffff rdx 0x3 rdi 0x664c18 rsi 0x0 rbp 0x7f669eb13c98 rsp 0x7fffa08123e8 r8 0x0 r9 0x0 r10 0x6601b0 r11 0x206 r12 0x250 r13 0x7fffa0812500 r14 0x0 r15 0x0 rip 0x411b25 rflags 0x206 cs 0x33 fs 0x0 gs 0x0 Trace/breakpoint trap
Comment 17 by m@capitanio.org:
Thanks, I didn't hit the reload button and missed the comments. BTW, the crock design is elaborated here ;) http://www.cs.utexas.edu/~witchel/372/lectures/POSIX_Linux_Threading.pdf
Comment 23 by tsar@cosmocode.de:
Is anyone working on that? If there are some experimental patches I would be happy to test them ;-) I added a little test case to my own little library to detect non-posix compliant systems (all the *bsd variants work as expected): https://github.com/sarnowski/mitigation
I'm looking into this, I have a start to the implementation of what rsc suggested above. I've updated the example for go1, along with using the os.Setuid/os.Setgid (an API change) from my soon-to-be-posted CL.
Attachments:
The cause of this problem seems closely related to the cause of daemonization problems; that is, the inability to consistently execute package main code on a vanilla runtime before any thread spawning occurs (as was trivial with the old init behavior), or alternatively, the ability to command the runtime to suspend goroutines at their next non-externally-blocked scheduling point, closing all but one thread (if the runtime can even temporarily coexist with app code in the main thread). Preemptive scheduling would certainly make the latter simpler.
Comment 35 by peter@scraperwiki.com:
If it is impossible to get a fix in for this for 1.3, could we at least get a documentation fix in (at syscall.Setuid et al.)? It would have saved me quite a number of hours. If so I can submit the change. I'd also be interested at trying to make the fix if it's not too late for 1.3.
Considering all the recent security issues going on, Heartbleed to name just one, I can't believe this issue is not taking the importance it deserves. Specially, since this is not even a problem while running on App Engine. Yes, that's right, on App Engine ListenAndServe is replaced by Google's own version of it. http://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath#TOC_2 "The App Engine infrastructure provides its own main function that runs its equivalent to ListenAndServe. To convert main.go to an App Engine app, drop the call to ListenAndServe and register the handler in an init function (which runs before main)." Which makes me wonder what kind of language is Google trying to create here. Is Google's Golang team in a way saying that to run the language on production you have to do it on their commercial cloud infrastructure? To think of it, this is something that only comes from commercially sponsored languages. Python, Perl or any other open source language that's community spearheaded wouldn't have this type of issue. Who would on the right mind run an http server as root? Come on guys, fix this. It's not that hard.
This is only an issue on GNU/Linux. The right way to handle the problem for a web server on GNU/Linux is to use setcap. So this does not seem as urgent to me as it apparently does to you. That said, of course it would be good to fix this. But I think it is harder than you think it is. I would be glad to be proven wrong.
Labels changed: added suggested, os-linux, removed priority-later.
Take a look at how NGINX handles this, line 36: http://trac.nginx.org/nginx/browser/nginx/src/os/unix/ngx_daemon.c#L36
digeratus, unfortunately, it is harder than you think to pull off setuid/setsid correctly. And as you point out, it is security sensitive, so it is important to get correct. You can find some discussion here: https://groups.google.com/forum/#!searchin/golang-dev/setuid$20implementing/golang-dev/sVCQl9adDgg/-EeOdMaWT48J Comparing to nginx is unhelpful, since it runs under a completely different runtime. A more apt comparison would be how glibc handles it: https://github.com/lattera/glibc/search?q=SIGSETXID&ref=cmdform Which involves sending and receiving an SIGSETXID signal and synchronizing all of the threads. I've had your exact frustration with this issue. It seems simple, but it isn't, sadly. i@golang.org: could you elaborate on the setcap solution?
listening on privilege ports on linux needs not the full root, but only a single capabilities(8) called CAP_NET_BIND_SERVICE. you can grant any capability on any executable file by using the setcap(8) command. e.g.: sudo setcap cap_net_bind_service=+ep go_server and then you can run go_server as a normal user (e.g. nobody).
> With all due respect, but if you don't consider GNU/Linux to be an important web server platform, I don't know what is. I don't know what you mean by that. Of course GNU/Linux is an important web server platform. > At the same time, how hard is it to do what requires "root" permission and the lower it to a lower permission user? On GNU/Linux, it's best to use the setcap mechanism, and not use root permission at all. setcap has been around since Linux 2.2, it's not some newfangled thing. That said, we all agree that this bug should be fixed. It would be great if you worked on fixing it, rather than simply telling us that it is easy to fix. Thanks.
Comment 45 by StevensElectronicMail:
Given that setuid is broken with Go can it be removed (or maybe somehow tagged with a warning) while we're waiting for a fix so that nobody uses it by accident. I know this would break backwards compatibility but in this case it SHOULD break backwards compatibility because code that uses it most probably has a security hole.
CL https://golang.org/cl/106170043 mentions this issue.
This issue was updated by revision 343b4ba8c1ad8a29b6dd19cb101273b57a26c9b.
This proposal disables Setuid and Setgid on all linux platforms. issue #1435 has been open for a long time, and it is unlikely to be addressed soon so an argument was made by a commenter https://golang.org/issue/1435?c=45 That these functions should made to fail rather than succeed in their broken state. LGTM=ruiu, iant R=iant, ruiu CC=golang-codereviews https://golang.org/cl/106170043
Can we fix this before Go 1.4 by any chance? I read the glibc code briefly. It looks like we need to (1) set a signal handler for SIGSETXID that calls setuid/setgid system call from the handler, and (2) signal all threads with SIGSETXID when setuid/setgid Go function is called. Because we don't provide syscall.sigaction, I think we don't need to worry about the case that a user accidentally disable SIGSETXID system call. Is there anything other than that we need to do for this?
Since Setuid is used for security, and is often used to drop privileges, it's fairly important to get it right. In a pure Go program with no cgo, I believe we can freeze the world and force each thread to use the system call. I don't think we need any signals in that case. The problem is a Go program that uses cgo. We have to assume that cgo code has created threads that we don't know about. We also have to assume that cgo code can call setuid. Because of that possibility, I don't think we can provide a SIGSETXID signal handler ourselves for a cgo program; see issue #3871. Adding this all together, it now occurs to me that it may work to make syscall.Setuid a function that uses cgo to call the C setuid function. That will force the use of cgo for any program that uses syscall.Setuid, meaning that every thread will be created using pthread_create and that we should get the C library signal handler for SIGSETXID. Want to see if that will work?
by ziutek@Lnet.pl:
Attachments: