Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upsyscall: Setuid/Setgid doesn't apply to all threads on Linux #1435
Comments
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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
gopherbot
commented
Jan 21, 2011
|
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
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alberts
Jan 21, 2011
Contributor
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.
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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 ...
gopherbot
commented
Jan 21, 2011
|
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 ... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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!
gopherbot
commented
Jan 21, 2011
|
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!
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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
gopherbot
commented
Jan 21, 2011
|
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
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Owner changed to r...@golang.org. Status changed to Accepted. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
rsc
Jan 21, 2011
Contributor
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.
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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:
- http.go (969 bytes)
gopherbot
commented
Jan 21, 2011
|
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:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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().
gopherbot
commented
Jan 21, 2011
|
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(). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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!
gopherbot
commented
Jan 21, 2011
|
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!
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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!
gopherbot
commented
Jan 21, 2011
|
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!
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
rsc
Jan 21, 2011
Contributor
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
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 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 21, 2011
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
gopherbot
commented
Jan 21, 2011
|
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
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 22, 2011
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
gopherbot
commented
Jan 22, 2011
|
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 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Jan 22, 2011
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...
gopherbot
commented
Jan 22, 2011
|
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... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
May 11, 2012
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
gopherbot
commented
May 11, 2012
|
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 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bpowers
Oct 9, 2012
Contributor
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:
- main.go (859 bytes)
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:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
extemporalgenome
Dec 11, 2012
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.
extemporalgenome
commented
Dec 11, 2012
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Dec 14, 2013
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.
gopherbot
commented
Dec 14, 2013
|
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Apr 23, 2014
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.
gopherbot
commented
Apr 23, 2014
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 comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Apr 23, 2014
Contributor
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.
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
commented
Apr 23, 2014
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Apr 23, 2014
Take a look at how NGINX handles this, line 36: http://trac.nginx.org/nginx/browser/nginx/src/os/unix/ngx_daemon.c#L36
gopherbot
commented
Apr 23, 2014
Take a look at how NGINX handles this, line 36: http://trac.nginx.org/nginx/browser/nginx/src/os/unix/ngx_daemon.c#L36 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
Apr 23, 2014
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?
gopherbot
commented
Apr 23, 2014
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? |
bradfitz
modified the milestones:
Go1.10Early,
Go1.10
Jun 14, 2017
jrouzierinverse
referenced this issue
Jun 15, 2017
Open
global: Files permissions / existence ++ #2376
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ivan4th
commented
Jun 21, 2017
|
See #20676 and https://golang.org/cl/46033 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
tsuna
Jun 21, 2017
Contributor
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.
|
Not sure whether #20676 will be of much help here since the problem is that on Linux |
TheTincho
referenced this issue
Jul 13, 2017
Closed
Dropping privileges might not work as expected #64
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
riking
Sep 27, 2017
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.
riking
commented
Sep 27, 2017
|
One method of fixing this correctly would be a mirror to
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 |
added a commit
to tklauser/runc
that referenced
this issue
Oct 16, 2017
added a commit
to tklauser/runc
that referenced
this issue
Oct 16, 2017
tklauser
referenced this issue
Oct 16, 2017
Merged
libcontainer: merge common syscall implementations #1613
added a commit
to tklauser/runc
that referenced
this issue
Oct 16, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
hgfischer
Dec 4, 2017
Contributor
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.
|
Will Setuid/Setgid someday be allowed again on Linux? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bradfitz
Dec 4, 2017
Member
@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.
|
@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
modified the milestones:
Go1.10,
Go1.11
Dec 4, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ivan4th
commented
Dec 4, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Dec 4, 2017
Contributor
@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).
|
@hgfischer If you just want to change the ID of one thread (but why?) you can use |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
hgfischer
Dec 4, 2017
Contributor
@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?
|
@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 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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Dec 5, 2017
Contributor
@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.
|
@hgfischer Yes. Go programs are always multi-threaded, goroutines can migrate between threads at any function call, and the old |
strib
referenced this issue
Jan 19, 2018
Merged
packaging: linux uses per-user mountpoint, /keybase is a symlink #10250
chutzimir
referenced this issue
Feb 23, 2018
Open
syscall.Setuid and syscall.Setgid fail on Linux #1
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nnnn20430
Mar 19, 2018
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
commented
Mar 19, 2018
|
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? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Mar 20, 2018
Contributor
@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.
|
@nnnn20430 There is no particular problem with starting another program using a new UID/GID. We already support that via 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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nnnn20430
Mar 20, 2018
@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... #1435 (comment)
nnnn20430
commented
Mar 20, 2018
•
|
@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 PPS: again it was late... now i see this was even said here before... #1435 (comment) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Mar 20, 2018
Contributor
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.
|
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 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. |
gopherbot commentedJan 21, 2011
by ziutek@Lnet.pl:
Attachments: