What steps will reproduce the problem? Compile attached test code. Run it as root like this: # GOMAXPROCS=4 ./test 65534 65534 and note output: gorutine 1: uid=0 euid=0 gid=0 egid=0 gorutine 2: uid=0 euid=0 gid=0 egid=0 gorutine 3: uid=0 euid=0 gid=0 egid=0 gorutine 4: uid=0 euid=0 gid=0 egid=0 gorutine 5: uid=0 euid=0 gid=0 egid=0 gorutine 6: uid=0 euid=0 gid=0 egid=0 gorutine 7: uid=0 euid=0 gid=0 egid=0 gorutine 8: uid=0 euid=0 gid=0 egid=0 gorutine 9: uid=0 euid=0 gid=0 egid=0 gorutine 0: uid=65534 euid=65534 gid=65534 egid=65534 gorutine 1: uid=0 euid=0 gid=0 egid=0 gorutine 2: uid=0 euid=0 gid=0 egid=0 gorutine 3: uid=0 euid=0 gid=0 egid=0 gorutine 4: uid=0 euid=0 gid=0 egid=0 gorutine 5: uid=0 euid=0 gid=0 egid=0 gorutine 6: uid=0 euid=0 gid=0 egid=0 gorutine 7: uid=0 euid=0 gid=0 egid=0 gorutine 8: uid=0 euid=0 gid=0 egid=0 gorutine 9: uid=0 euid=0 gid=0 egid=0 gorutine 0: uid=65534 euid=65534 gid=65534 egid=65534 Use ps -efL during test execution and note output: UID PID PPID LWP C NLWP STIME TTY TIME CMD nobody 26088 25928 26088 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26089 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26090 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26091 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26092 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26093 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26094 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26095 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26096 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 root 26088 25928 26097 0 10 11:56 pts/1 00:00:00 ./test 65534 65534 What is the expected output? All threads must have the same UID/GID: (65534, nobody user in my system). Which compiler are you using (5g, 6g, 8g, gccgo)? I tested this with 6g and 8g. Which operating system are you using? Linux (Debian 6.0 SID on i386, Ubuntu 10.10 on amd64) Which revision are you using? (hg identify) d8ba80011a98 release/release.2011-01-20 Please provide any additional information below. http://groups.google.com/group/golang-nuts/browse_thread/thread/59597aafdd84a0e
Attachments:
Comment 1 by [email protected]:
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.
Comment 4 by [email protected]:
You could use runtime.Goroutines() to wait if you are the only one. If it isn't possible to terminate all "someHousekeepingFunc()" e.g. with Goexit(), I think setuid gains you to security not much ...
Comment 5 by [email protected]:
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!
Comment 6 by [email protected]:
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._
Comment 9 by [email protected]:
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:
Comment 10 by [email protected]:
Sorry. I had not noticed it before, but this is also when there is no runtime.LockOSThread call. This is probably due to rescheduling between syscall.Getuid() and syscall.Getgid().
Comment 12 by [email protected]:
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!
Comment 13 by [email protected]:
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 [email protected]:
>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 [email protected]:
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 18 by [email protected]:
I suggest put a clear warning in the header of syscall package about issues that may cause the use of this package. Maybe a list of links to known issues for each function would not be a bad idea...
Comment 23 by [email protected]:
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 [email protected]:
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. [email protected]: 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?
@iant I'm assuming this approach could fix Unshare too? https://golang.org/issue/1954
Calling the C library version of unshare wouldn't make any difference. The unshare function in the GNU/Linux libc only applies to the current thread. Since goroutines aren't attached to a specific thread, there is no meaningful way to call unshare from a goroutine. We would have to introduce a mechanism to call unshare from every thread, which is more or less what we are trying to avoid, and which is in any case impossible if cgo code has created new threads. I think the only way to use unshare from a Go program is to use runtime.LockOSThread. Or to arrange to call it just before a fork. I don't think there is any reasonable way to unshare across the whole process, and the same is true of C code.
How do you force the use of cgo for any program that uses syscall.Setuid? The only way I know how to do this would be to force the use of cgo for any program that uses syscall, but that's untenable. What we do in the runtime is check to see if cgo is already being used for some other reason, and if so we use it for things like pthread_create but otherwise fall back to our own implementation. I think we'd have to do something similar to play nicely with cgo here.
Does setreuid apply to all threads? Would this be a work around when trying to use it to temporarily go to root?
All the related system calls apply only to a single thread. The full set is setuid setgid setgroups setreuid setregid setresuid setresgid (and the xxx32 variants were applicable).
Any updates on this ?
Thanks
No.
I would like to add that the suggested workaround of using capabilities does not work. In order to be useful for security, capabilities need to be dropped by the process after performing the privileged function. However just as you cannot setuid() other threads than your own, you cannot drop capabilities of other threads than your own. So the other threads created by the Go runtime keep the elevated privileges granted by the capabilities.
This is a big problem. E.g. the capability CAP_SYS_CHROOT is worthless if you cannot drop it, since that capability can be used to break out of chroots.
So even with the best effort using SystemD and file capabilities it is currently not possible to create a Go program that performs some privileged task and then drops ALL of its privileges. This is embarassing.
A better solution is for Go to provide a way to serve on an already-open socket FD. A launcher (written in C) could then open the socket, drop privileges, and exec()
the Go program.
Go could do that already.
Use https://golang.org/pkg/os/#NewFile and
https://golang.org/pkg/net/#FileListener.
This could use a lot of tidying, but as a proof of concept: http://play.golang.org/p/dXBizm4xl3
When run as root, the program will open a privileged socket and re-exec itself as an unprivileged user passing down the opened FD.
% go build drop.go
% sudo ./drop
Dropping privileges...
Spawned process 30278, exiting
% curl 127.0.0.1
I am process 30278 running as 65534/65534
@bgilmore Thank you, that's just what I was about to start coding up. Really glad to see it works! (Can't believe I didn't think of this earlier...) Although it would be nice to do it in-process, this effectively solves the issue for me.
This trick should be documented as the way to drop priviliges in Go.
On Jan 21, 2016 11:46 PM, "Matt Holt" [email protected] wrote:
@bgilmore https://github.com/bgilmore Thank you, that's just what I was
about to start coding up. Really glad to see it works! (Can't believe I
didn't think of this earlier...) Although it would be nice to do it
in-process, this effectively solves the issue for me.—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/1435#issuecomment-173805699.
Unfortunately, this does not work if you chroot, which is another one of the most important security measures together with dropping privileges. E.g. if you implement a service like Dropbox or Google Drive where naturally users have the ability to upload arbitrary files, it's a very good idea to chroot into a filesystem mounted no-exec and together with setuid this will protect you against so many common programming errors.
I'd suggest the GOOS=linux behavior for Setuid/Setgid is correct for all operating systems and should be extended to Chroot for all OS's. I don't think any user code should be able to change the process state of the runtime in such a way other user code might not be able to do something it was able to do before the process state of the runtime was changed. For example any package that relies on any kind of file access needs to assume the state of the runtime will be reasonably consistent and shouldn't have to assume something can change the effective root of the runtime at any time without notice. It should be pretty reasonable to assume a package can create a temporary file and write to it at any time without worrying some other user code could at any time change uid/gid and make it unwritable.
Using setuid/gid to "drop privileges" in a Go program would tend to give a false sense of security anyway, the obvious place to do it would be a main package's init() and that's the last init() to run if there's multiple, and would always lead to the case above where things were possible for an imported package's init() that aren't after main's init(). Setuid, Setgid and Chroot just introduce potential opaque, hard to debug chaos and provide little or no value.
Setuid, Setgid and Chroot just introduce potential opaque, hard to debug chaos and provide little or no value.
I beg to differ about the "little or no value" part. I'd like to see something like openbsds pledge eventually, though I realize that's probably not something that will happen anytime soon.
I think Go shouldn't schedule goroutines from a locked thread on another thread. goroutines are often used for control flow, and introducing a goroutine wouldn't be a "breaking change" in many cases in code you depend on... so it's hard to use Go when you really need a thread to be locked (e.g. when managing network namespaces).
See #20676 and https://golang.org/cl/46033
Not sure whether #20676 will be of much help here since the problem is that on Linux setuid
/ setgid
only affect the current thread instead of the entire process. Usually the goal of these calls is to drop/change privileges for the entire process, not just temporarily affect the privileges of the current thread.
One method of fixing this correctly would be a mirror to runtime.systemstack
; e.g. runtime.everyM
.
// Call Setuid for every OS thread in the process.
func Setuid(uid int) error {
errCh := make(chan syscall.Errno)
runtime.everyM(func() {
_, _, err := syscall.Syscall(syscall.SYS_SETUID, uid, 0, 0)
if err != nil {
errCh <- err
}
})
select {
case err := <-errCh:
return err
default:
return nil
}
}
// and a Windows example...
// Call SetThreadToken for every OS thread of the process.
func SwitchToken(tokenHandle syscall.Handle) error {
errCh := make(chan syscall.Errno)
runtime.everyM(func() {
r1, _, e1 := syscall.Syscall(procSetThreadToken.Addr(), 0, uintptr(tokenHandle), 0)
if r1 == 0 {
if e1 != 0 {
errCh <- errnoErr(e1)
} else {
errCh <- syscall.EINVAL
}
}
})
select {
case err := <-errCh:
return err
default:
return nil
}
}
The semantics of what happens when a machine thread is already in a syscall are currently unspecified, but sending a reserved signal like glibc does as a trigger to check for everyM
tasks seems reasonable. Syscalls are already restarted on EINTR.
Will Setuid/Setgid someday be allowed again on Linux?
I have a use-case for it and I don't even bother that it just works in the main thread.
@hgfischer, what do you mean by "again"? I don't think this ever worked in Go.
In any case, bumping to Go 1.11 because this didn't happen for Go 1.10.
@bradfitz to be more precise, it used to be enabled but didn't really work. It was then disabled in 343b4ba8c1ad8a29b6dd19cb101273b57a26c9b0
@hgfischer If you just want to change the ID of one thread (but why?) you can use runtime.LockOSThread
and syscall.Syscall(syscall.SYS_SETUID, uid, 0, 0)
.
@bradfitz Thanks. As @ivan4th mentioned, it used to do something, some time ago, but then it got disabled because of this issue.
@ianlancetaylor Thanks for the tips. I tried just syscall.Syscall()
before but it was not working for some reason. Since you mentioned, I tried a bit more and _voilá_!
The reason is to have a binary with setuid bit, that can be called by an unpriviledged user. I don't need goroutines for this, and the tool will be short lived. My test is here.
Could I still face the problems reported in this issue, in this use case?
@hgfischer Yes. Go programs are always multi-threaded, goroutines can migrate between threads at any function call, and the old syscall.Setuid
only changed one thread in the program.
Couldn't this be solved by adding setuid/setguid parameters to syscall.SysProcAttr, and then have it call setuid/setgid in a thread safe way right before exec() when starting a new process?
So that it can at least set uid of a new process, and then you can just re-execute the program and have the original instance exit, successfully achieving privilege drop/escalation.
@nnnn20430 There is no particular problem with starting another program using a new UID/GID. We already support that via SysProcAttr.Credential
.
The problem is with programs that want to change their effective uid/gid while executing. This is a more or less reasonable thing for a Unix program to do, but it doesn't currently work for Go programs on GNU/Linux. That is what this issue is about.
@ianlancetaylor ah oops, i just looked at surface level entries in the type didn't see setuid and assumed it to not be there, i was just thinking "can't this be solved like this?", and just skimmed over the type to confirm if it was there or not before commenting, sorry that was dumb....
PS: basically it was late and i wanted to comment before i forget and go to sleep
comment
PPS: again it was late... now i see this was even said here before... https://github.com/golang/go/issues/1435#issuecomment-66054177
I want to note that I think that any fix for this issue has to be quite different in pure Go programs and in programs that use cgo. In cgo-using programs I think we need to somehow use the glibc signal handler for SIGSETXID
(SIGRTMIN + 1
) and use the glibc call for setuid
, setgid
, setresgid
, setgroups
, setegid
, setregid
, setresuid
, seteuid
, setreuid
, etc.
In pure Go programs we might be able to get away with queuing up a list of changes for each thread, and having the thread apply them every time it acquires a lock. My thinking here is that it's OK if the thread only reflects the UID change after there is some happens-before relationship between the UID call and the thread execution, and that should be mediated by a lock.
So is it safe to call glibc's version of setuid
(which implies using CGO) just to setup the UID for all OS threads within the process? I've tested that and it seems to work but don't know if it will have any impact on the correctness of goroutine scheduling?
@ironsteel I can't think of a reason that that wouldn't work, but I also wouldn't be surprised if there were cases where it failed. One particular case to consider is if a new thread is created during the setuid
call.
The page is the top result in the google search. Thus I put an arguably verbose example of fork/exec here.
If do not like syscall.Syscall(syscall.SYS_SETUID, uintptr(uid), 0, 0)
you can do this:
// Read environment variable USER, fork the process
func SwitchUser(daemon bool) (*user.User, error) {
userCurrent, err := user.Current()
if err != nil {
return nil, fmt.Errorf("Failed to get current user %v", err)
}
userRequired := userCurrent
if v := os.Getenv("USER"); v != "" { // Rely on the environment
userRequired, err = user.Lookup(v) // and make it a truly a single liner in the calling code
if err != nil {
return userCurrent, fmt.Errorf("Failed to lookup '%s' %v", v, err)
}
}
if userRequired.Uid == userCurrent.Uid {
return userCurrent, nil
}
if uid := os.Getuid(); uid != 0 { // Straight from the exec unitest
return userCurrent, fmt.Errorf("I need root credentials, got %v instead", uid)
}
executable, err := filepath.Abs(filepath.Dir(os.Args[0])) // Who am I? What is me?
if err != nil {
return userCurrent, fmt.Errorf("Failed to get path args[0]=%s %v", os.Args[0], err)
}
executable = path.Join(executable, path.Base(os.Args[0]))
cmd := exec.Command(executable)
gid, _ := strconv.Atoi(userRequired.Gid) // this is the magic part
uid, _ := strconv.Atoi(userRequired.Uid) // run the same executable
cmd.SysProcAttr = &syscall.SysProcAttr{ // with a different user
Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
}
cmd.Stdout = os.Stdout // you probably want to see the child's logs
cmd.Stderr = os.Stderr // in the parent's stdout
cmdRun := cmd.Run
if daemon {
cmdRun = cmd.Start
}
if err := cmdRun(); err != nil {
// Or exec failed or the child failed
return userCurrent, fmt.Errorf("Failed to run %s as user %s %v", executable, userRequired.Name, err)
} else {
// Or daemon is true, or the child did Exit(0)
return userRequired, nil
}
}
Change https://golang.org/cl/210639 mentions this issue: syscall: Support POSIX semantics for Linux syscalls.
> // 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) }
You could instead do what I do, specifically to avoid having a Go program run even briefly as root, but still able to listen on 80 and 443: use CAP_NET_BIND_SERVICE=+eip. I use a command "bless", attached, to set the attribute each time the file is updated from my build server. The executable name is fixed, so bless itself can be setuid, but you could add an argument instead.
bless.c.txt
The < 1024 restriction is, of course, long obsolete. It wasn't even all that sensible when BSD had it for their timesharing systems "to make them more secure".
@forsyth Unfortunately, this is a linux-only thing.
I hadn't noticed this item was so old and undoubtedly the original user's need has long since gone away. Sadly, the "privileged port" stuff, let alone root itself hasn't gone away in the 8 years since.
An alternative to CAP_NET_BIND_SERVICE=+eip is to give CAP_NET_BIND_SERVICE as ambient capability without marking the exe as such. This way only invocations via the service manager will be allowed to bind to the privileged port, not an arbitrary invocation. Systemd allows that with one line in the service file via AmbientCapabilities=. Or one can use setpriv utility.
If you're using systemd, you can just let it pass an open listening fd in as it executes your app. Also nicely limits what ports your app can use, with complete admin control.
Maybe these low port workarounds should be marked as off-topic. They're not helping solve the actual problem; try reimplementing samba or opensshd in Go and you'll hit this limit pretty quickly.
New to this issue tracker. Happy to be assigned this issue. I have a pending change to fix it here:
On the subject of capabilities and Go... I'd like to point out that the latest libcap-2.29 sources include a full port of libcap to Go ( see the official sources for libcap and release notes here: https://sites.google.com/site/fullycapable/ )
Using cgo and a C library libpsx for now, capability raising and lowering of all sets including the ambient one are fully supported in the Go package.
The native (CGO_ENABLED=0) build of the Go package requires the above https://go-review.googlesource.com/c/go/+/210639 patch to run. The libcap source tree includes a small webserver example:
https://git.kernel.org/pub/scm/libs/libcap/libcap.git/tree/go/web.go
-- update:
to add Go module build support, I've moved the web.go sources to:
https://git.kernel.org/pub/scm/libs/libcap/libcap.git/tree/goapps/web
I don't seem to be able to edit the tags associated with this bug. I'm not sure help is wanted for the code, but would appreciate any real world testing if folk want to stress test the proposed patch...
FYI As a follow up to https://github.com/golang/go/issues/1435#issuecomment-569543665 the latest two releases of libcap also contain Go module buildable versions of the libcap/cap and libcap/psx packages. I've done a write up of the web.go example (which I moved around to make it buildable with the module system) here:
This doesn't address the native Go build issue pending a solution in the form of https://go-review.googlesource.com/c/go/+/210639 patch, but it does allow general support for mirroring system calls in a combined Go/CGo runtime.
The modified instructions over that write up for validating and getting started exploring the above patch are to do this to build the web binary:
$ mkdir bar
$ cd bar
$ git clone https://go.googlesource.com/go
$ cd go
$ git fetch https://go.googlesource.com/go refs/changes/39/210639/38 && git checkout -b change-210639 FETCH_HEAD
$ cd src
$ ./make.bash
$ cd ../..
$ mkdir foo
$ cd foo
$ wget https://git.kernel.org/pub/scm/libs/libcap/libcap.git/plain/goapps/web/web.go
$ ../go/bin/go mod init web
$ CGO_ENABLED=0 ../go/bin/go build -tags allthreadssyscall web.go
$ ldd web
not a dynamic executable
You can follow the rest of https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities for how to use it.
I realize that the above is only just on topic for the initial subject of this bug ;) Here is an explanation of how to use the same packages to address the reason this bug was filed (currently via cgo, while the AllThreadsSyscall patch is still pending):
https://sites.google.com/site/fullycapable/getting-started-with-go/using-go-to-set-uid-and-gids
Note, one build target linux-ppc64 appears to fail the added tests for this feature. Resolving that is the subject of #42178.
Happy to see this is fixed, but when will this be "backported", if at all? What Go version will this fix occur in?
This change is currently included in the 1.16 development branch. No currently released version of Go contains it.
Most helpful comment
Change https://golang.org/cl/210639 mentions this issue:
syscall: Support POSIX semantics for Linux syscalls.