New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
syscall: Setuid/Setgid doesn't apply to all threads on Linux #1435
Comments
|
Comment 1 by m@capitanio.org: 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 m@capitanio.org: 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 ziutek@Lnet.pl: 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 m@capitanio.org: 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
|
|
Owner changed to r...@golang.org. Status changed to Accepted. |
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 ziutek@Lnet.pl: 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 ziutek@Lnet.pl: 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 ziutek@Lnet.pl: 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 ziutek@Lnet.pl: 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 18 by ziutek@Lnet.pl: 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 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. |
|
Yes. I've also authored a native Go package for manipulating capabilities at runtime: |
|
@AndrewGMorgan thanks for sharing. |
The program web.go uses "libcap/cap" to raise and lower capabilities in order to bind to a privileged port. Writing this code, I now realize that Go's runtime is not really suited to minimal privilege guarantees. The code does raise and lower the effective capability Value needed, but to be fully robust, we're going to have to wait for the following issue with the Go runtime to find a resolution: golang/go#1435 Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
I've moved my go.patch to address: golang/go#1435 into a development patch against the upstream Go sources: https://go-review.googlesource.com/c/go/+/210639/ and the review process will likely evolve it somewhat. I plan to ensure that working libcap/cap Go package is in sync with the working state of the above development change. As such, there is no need to keep the patch here any more. I'll keep the tests for now, as it isn't clear to me how the Go source tree supports tests that require privilege yet. Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
This is something pretty fundamental that a number of folk have asked about. It is essentially the motivating issue for: golang/go#1435 Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
On FreeBSD, changing a process's supplementary groups with setgroups(2) will also change the egid of the process, setting it to the first entry in the provided list. This is distinct from the behaviour on other platforms (and possibly a violation of the POSIX standard). Because of this, our incubator code wouldn't change the process's gid, because it would read the newly-changed egid, compare it against the expected egid, and since they matched, not change the gid. Fix this by ensuring that the setGroups call behaves the same on FreeBSD as it does on other platforms by adding the current egid as the first entry in the list, and thus not changing it unexpectedly. Also, while we're here... defensively call runtime.LockOSThread to make it less likely we trip over bugs like golang/go#1435 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Icc08c629ddc87923aa9ae0d542f63925e9c40cd3
On FreeBSD, changing a process's supplementary groups with setgroups(2)
will also change the egid of the process, setting it to the first entry
in the provided list. This is distinct from the behaviour on other
platforms (and possibly a violation of the POSIX standard).
Because of this, our incubator code wouldn't change the process's gid,
because it would read the newly-changed egid, compare it against the
expected egid, and since they matched, not change the gid.
This could be observed by running "id -p" in two contexts. The expected
output, and the output returned when running from a SSH shell, is:
andrew@freebsd:~ $ id -p
uid andrew
groups andrew
However, when run via "ssh andrew@freebsd id -p", the output would be:
$ ssh andrew@freebsd id -p
login root
uid andrew
rgid wheel
groups andrew
(this could also be observed via "id -g -r" to print just the gid)
We fix this by ensuring that the setGroups call behaves the same on
FreeBSD as it does on other platforms by adding the current egid as the
first entry in the list, and thus not changing it unexpectedly.
Also, while we're here... defensively call runtime.LockOSThread to make
it less likely we trip over bugs like golang/go#1435
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Icc08c629ddc87923aa9ae0d542f63925e9c40cd3
On FreeBSD, changing a process's supplementary groups with setgroups(2)
will also change the egid of the process, setting it to the first entry
in the provided list. This is distinct from the behaviour on other
platforms (and possibly a violation of the POSIX standard).
Because of this, our incubator code wouldn't change the process's gid,
because it would read the newly-changed egid, compare it against the
expected egid, and since they matched, not change the gid.
This could be observed by running "id -p" in two contexts. The expected
output, and the output returned when running from a SSH shell, is:
andrew@freebsd:~ $ id -p
uid andrew
groups andrew
However, when run via "ssh andrew@freebsd id -p", the output would be:
$ ssh andrew@freebsd id -p
login root
uid andrew
rgid wheel
groups andrew
(this could also be observed via "id -g -r" to print just the gid)
We fix this by ensuring that the setGroups call behaves the same on
FreeBSD as it does on other platforms by adding the current egid as the
first entry in the list, and thus not changing it unexpectedly.
More information can be found in the following article:
https://www.usenix.org/system/files/login/articles/325-tsafrir.pdf
Also, while we're here... defensively call runtime.LockOSThread to make
it less likely we trip over bugs like golang/go#1435
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Icc08c629ddc87923aa9ae0d542f63925e9c40cd3
On FreeBSD, changing a process's supplementary groups with setgroups(2)
will also change the egid of the process, setting it to the first entry
in the provided list. This is distinct from the behaviour on other
platforms (and possibly a violation of the POSIX standard).
Because of this, our incubator code wouldn't change the process's gid,
because it would read the newly-changed egid, compare it against the
expected egid, and since they matched, not change the gid.
This could be observed by running "id -p" in two contexts. The expected
output, and the output returned when running from a SSH shell, is:
andrew@freebsd:~ $ id -p
uid andrew
groups andrew
However, when run via "ssh andrew@freebsd id -p", the output would be:
$ ssh andrew@freebsd id -p
login root
uid andrew
rgid wheel
groups andrew
(this could also be observed via "id -g -r" to print just the gid)
We fix this by ensuring that the setGroups call behaves the same on
FreeBSD as it does on other platforms by adding the current egid as the
first entry in the list, and thus not changing it unexpectedly.
More information can be found in the following article:
https://www.usenix.org/system/files/login/articles/325-tsafrir.pdf
Also, while we're here... defensively call runtime.LockOSThread to make
it less likely we trip over bugs like golang/go#1435
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Icc08c629ddc87923aa9ae0d542f63925e9c40cd3
On FreeBSD and Darwin, changing a process's supplementary groups with
setgroups(2) will also change the egid of the process, setting it to the
first entry in the provided list. This is distinct from the behaviour on
other platforms (and possibly a violation of the POSIX standard).
Because of this, our incubator code wouldn't change the process's gid,
because it would read the newly-changed egid, compare it against the
expected egid, and since they matched, not change the gid.
This could be observed by running "id -p" in two contexts. The expected
output, and the output returned when running from a SSH shell, is:
andrew@freebsd:~ $ id -p
uid andrew
groups andrew
However, when run via "ssh andrew@freebsd id -p", the output would be:
$ ssh andrew@freebsd id -p
login root
uid andrew
rgid wheel
groups andrew
(this could also be observed via "id -g -r" to print just the gid)
We fix this by ensuring that the setGroups call behaves the same on
FreeBSD as it does on other platforms by adding the current egid as the
first entry in the list, and thus not changing it unexpectedly.
More information can be found in the following article:
https://www.usenix.org/system/files/login/articles/325-tsafrir.pdf
Also, while we're here... defensively call runtime.LockOSThread to make
it less likely we trip over bugs like golang/go#1435
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Icc08c629ddc87923aa9ae0d542f63925e9c40cd3
|
The |
|
Sorry, we don't try to set the milestone when we close an issue. But, yes, this was fixed in the 1.16 release. |
This makes it less likely that we trip over bugs like golang/go#1435. Updates #7616 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ic28c03c3ad8ed5274a795c766b767fa876029f0e
This makes it less likely that we trip over bugs like golang/go#1435. Updates #7616 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ic28c03c3ad8ed5274a795c766b767fa876029f0e
This makes it less likely that we trip over bugs like golang/go#1435. Updates #7616 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ic28c03c3ad8ed5274a795c766b767fa876029f0e
This makes it less likely that we trip over bugs like golang/go#1435. Updates tailscale#7616 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ic28c03c3ad8ed5274a795c766b767fa876029f0e
Since Go 1.16, [Go issue 1435][1] is solved, and the stdlib syscall implementations work on Linux. While they are a bit more flexible/heavier-weight than the implementations that were copied to libcontainer/system (working across all threads), we compile with Cgo, and using the libc wrappers should be just as suitable. [1]: golang/go#1435 Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Since Go 1.16, [Go issue 1435][1] is solved, and the stdlib syscall implementations work on Linux. While they are a bit more flexible/heavier-weight than the implementations that were copied to libcontainer/system (working across all threads), we compile with Cgo, and using the libc wrappers should be just as suitable. [1]: golang/go#1435 Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Since Go 1.16, [Go issue 1435][1] is solved, and the stdlib syscall implementations work on Linux. While they are a bit more flexible/heavier-weight than the implementations that were copied to libcontainer/system (working across all threads), we compile with Cgo, and using the libc wrappers should be just as suitable. [1]: golang/go#1435 Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Since Go 1.16, [Go issue 1435][1] is solved, and the stdlib syscall implementations work on Linux. [1]: golang/go#1435 Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Since Go 1.16, [Go issue 1435][1] is solved, and the stdlib syscall implementations work on Linux. [1]: golang/go#1435 Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Since Go 1.16, [Go issue 1435][1] is solved, and the stdlib syscall implementations work on Linux. [1]: golang/go#1435 Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
by ziutek@Lnet.pl:
Attachments:
The text was updated successfully, but these errors were encountered: