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

Open
gopherbot opened this Issue Jan 21, 2011 · 81 comments

Comments

Projects
None yet
@gopherbot

gopherbot commented Jan 21, 2011

by ziutek@Lnet.pl:

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:

  1. test.go (1067 bytes)
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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
@alberts

This comment has been minimized.

Show comment
Hide comment
@alberts

alberts Jan 21, 2011

Contributor

Comment 2:

By the time main starts executing it might be too late to be sure that you cover all the
goroutines.
A package could do:
func init() {
    go someHousekeepingFunc()
}
which would then have the wrong uid/gid.
Contributor

alberts commented Jan 21, 2011

Comment 2:

By the time main starts executing it might be too late to be sure that you cover all the
goroutines.
A package could do:
func init() {
    go someHousekeepingFunc()
}
which would then have the wrong uid/gid.
@alberts

This comment has been minimized.

Show comment
Hide comment
@alberts

alberts Jan 21, 2011

Contributor

Comment 3:

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.
Contributor

alberts commented Jan 21, 2011

Comment 3:

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.
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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 ...
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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!
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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
@robpike

This comment has been minimized.

Show comment
Hide comment
@robpike

robpike Jan 21, 2011

Contributor

Comment 7:

Owner changed to r...@golang.org.

Status changed to Accepted.

Contributor

robpike commented Jan 21, 2011

Comment 7:

Owner changed to r...@golang.org.

Status changed to Accepted.

@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Jan 21, 2011

Contributor

Comment 8:

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.

Contributor

rsc commented Jan 21, 2011

Comment 8:

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.

@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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:

  1. 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:

  1. http.go (969 bytes)
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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().
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Jan 21, 2011

Contributor

Comment 11:

Yes, even with LockOSThread it only applies to the current goroutine.
If you check from another goroutine, you may or may not see the change.
Sorry.
Another possible workaround for your specific case is to use setcap(8)
to give the binary the CAP_NET_BIND_SERVICE capability only.
Contributor

rsc commented Jan 21, 2011

Comment 11:

Yes, even with LockOSThread it only applies to the current goroutine.
If you check from another goroutine, you may or may not see the change.
Sorry.
Another possible workaround for your specific case is to use setcap(8)
to give the binary the CAP_NET_BIND_SERVICE capability only.
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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!
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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!
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Jan 21, 2011

Contributor

Comment 14:

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
Contributor

rsc commented Jan 21, 2011

Comment 14:

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
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Jan 21, 2011

Contributor

Comment 16:

Yes, the LockOSThread workaround only applies to one goroutine.
Contributor

rsc commented Jan 21, 2011

Comment 16:

Yes, the LockOSThread workaround only applies to one goroutine.
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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...
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Jan 22, 2011

Contributor

Comment 19:

The syscall package is not for general use.
It has no documentation.  That's not going
to change.  When we have a working Setuid etc
they will be made available as part of package os.
Russ
Contributor

rsc commented Jan 22, 2011

Comment 19:

The syscall package is not for general use.
It has no documentation.  That's not going
to change.  When we have a working Setuid etc
they will be made available as part of package os.
Russ
@alberts

This comment has been minimized.

Show comment
Hide comment
@alberts

alberts Mar 30, 2011

Contributor

Comment 20:

Revision a25343ee3016 has added a way of doing Setuid and Setgid as part of
StartProcess, which might help some people.
Contributor

alberts commented Mar 30, 2011

Comment 20:

Revision a25343ee3016 has added a way of doing Setuid and Setgid as part of
StartProcess, which might help some people.
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Dec 9, 2011

Contributor

Comment 21:

Labels changed: added priority-later.

Contributor

rsc commented Dec 9, 2011

Comment 21:

Labels changed: added priority-later.

@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc May 15, 2012

Contributor

Comment 24:

No one is working on this as far as I know.
Russ
Contributor

rsc commented May 15, 2012

Comment 24:

No one is working on this as far as I know.
Russ
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Sep 12, 2012

Contributor

Comment 25:

Labels changed: added go1.1.

Contributor

rsc commented Sep 12, 2012

Comment 25:

Labels changed: added go1.1.

@bpowers

This comment has been minimized.

Show comment
Hide comment
@bpowers

bpowers Oct 9, 2012

Contributor

Comment 26:

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:

  1. main.go (859 bytes)
Contributor

bpowers commented Oct 9, 2012

Comment 26:

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:

  1. main.go (859 bytes)
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Dec 10, 2012

Contributor

Comment 27:

Labels changed: added size-l.

Contributor

rsc commented Dec 10, 2012

Comment 27:

Labels changed: added size-l.

@extemporalgenome

This comment has been minimized.

Show comment
Hide comment
@extemporalgenome

extemporalgenome Dec 11, 2012

Comment 28:

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

Comment 28:

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.
@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Mar 12, 2013

Contributor

Comment 29:

We didn't get to this in time. Going to have to wait for Go 1.2 (or maybe someone will
fix the Linux kernel!).

Labels changed: added go1.2, removed go1.1.

Contributor

rsc commented Mar 12, 2013

Comment 29:

We didn't get to this in time. Going to have to wait for Go 1.2 (or maybe someone will
fix the Linux kernel!).

Labels changed: added go1.2, removed go1.1.

@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Jul 30, 2013

Contributor

Comment 30:

Labels changed: added go1.3maybe, removed go1.2.

Contributor

rsc commented Jul 30, 2013

Comment 30:

Labels changed: added go1.3maybe, removed go1.2.

@robpike

This comment has been minimized.

Show comment
Hide comment
@robpike

robpike Aug 20, 2013

Contributor

Comment 31:

Labels changed: removed go1.3maybe.

Contributor

robpike commented Aug 20, 2013

Comment 31:

Labels changed: removed go1.3maybe.

@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Nov 27, 2013

Contributor

Comment 32:

Labels changed: added go1.3maybe.

Contributor

rsc commented Nov 27, 2013

Comment 32:

Labels changed: added go1.3maybe.

@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Dec 4, 2013

Contributor

Comment 33:

Labels changed: added release-none, removed go1.3maybe.

Contributor

rsc commented Dec 4, 2013

Comment 33:

Labels changed: added release-none, removed go1.3maybe.

@rsc

This comment has been minimized.

Show comment
Hide comment
@rsc

rsc Dec 4, 2013

Contributor

Comment 34:

Labels changed: added repo-main.

Contributor

rsc commented Dec 4, 2013

Comment 34:

Labels changed: added repo-main.

@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

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.
@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor Dec 14, 2013

Contributor

Comment 36:

It's certainly not too late for 1.3.  If you want to try to fix this, go for it.  You
might want to send around your proposal for how to fix it first.
Contributor

ianlancetaylor commented Dec 14, 2013

Comment 36:

It's certainly not too late for 1.3.  If you want to try to fix this, go for it.  You
might want to send around your proposal for how to fix it first.
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

gopherbot Apr 23, 2014

Comment 37 by digeratus:

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

Comment 37 by digeratus:

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.
@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor Apr 23, 2014

Contributor

Comment 38:

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.

Contributor

ianlancetaylor commented Apr 23, 2014

Comment 38:

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.

@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

gopherbot Apr 23, 2014

Comment 39 by digeratus:

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. At the same time, how hard is it to do what requires
"root" permission and the lower it to a lower permission user?

gopherbot commented Apr 23, 2014

Comment 39 by digeratus:

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. At the same time, how hard is it to do what requires
"root" permission and the lower it to a lower permission user?
@gopherbot

This comment has been minimized.

Show comment
Hide comment
@gopherbot

gopherbot Apr 23, 2014

Comment 40 by digeratus:

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

Comment 40 by digeratus:

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

This comment has been minimized.

Show comment
Hide comment
@gopherbot

gopherbot Apr 23, 2014

Comment 41 by peter.waller:

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

Comment 41 by peter.waller:

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?
@ivan4th

This comment has been minimized.

Show comment
Hide comment
@tsuna

This comment has been minimized.

Show comment
Hide comment
@tsuna

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.

Contributor

tsuna commented Jun 21, 2017

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.

@riking

This comment has been minimized.

Show comment
Hide comment
@riking

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 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.

tklauser added a commit to tklauser/runc that referenced this issue Oct 16, 2017

libcontainter: merge common syscall implementations
There are essentially two possible implementations for Setuid/Setgid on
Linux, either using SYS_SETUID32/SYS_SETGID32 or SYS_SETUID/SYS_SETGID,
depending on the architecture (see golang/go#1435 for why Setuid/Setgid
aren currently implemented for Linux neither in syscall nor in
golang.org/x/sys/unix).

Reduce duplication by mergung the currently implemented variants and
adjusting the build tags accordingly.

Signed-off-by: Tobias Klauser <tklauser@distanz.ch>

tklauser added a commit to tklauser/runc that referenced this issue Oct 16, 2017

libcontainter: merge common syscall implementations
There are essentially two possible implementations for Setuid/Setgid on
Linux, either using SYS_SETUID32/SYS_SETGID32 or SYS_SETUID/SYS_SETGID,
depending on the architecture (see golang/go#1435 for why Setuid/Setgid
aren currently implemented for Linux neither in syscall nor in
golang.org/x/sys/unix).

Reduce duplication by mergung the currently implemented variants and
adjusting the build tags accordingly.

Signed-off-by: Tobias Klauser <tklauser@distanz.ch>

tklauser added a commit to tklauser/runc that referenced this issue Oct 16, 2017

libcontainer: merge common syscall implementations
There are essentially two possible implementations for Setuid/Setgid on
Linux, either using SYS_SETUID32/SYS_SETGID32 or SYS_SETUID/SYS_SETGID,
depending on the architecture (see golang/go#1435 for why Setuid/Setgid
aren currently implemented for Linux neither in syscall nor in
golang.org/x/sys/unix).

Reduce duplication by merging the currently implemented variants and
adjusting the build tags accordingly.

Signed-off-by: Tobias Klauser <tklauser@distanz.ch>

@anxiousmodernman anxiousmodernman referenced this issue Oct 26, 2017

Closed

Initial requirements #1

0 of 14 tasks complete
@hgfischer

This comment has been minimized.

Show comment
Hide comment
@hgfischer

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.

Contributor

hgfischer commented Dec 4, 2017

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.

@bradfitz

This comment has been minimized.

Show comment
Hide comment
@bradfitz

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.

Member

bradfitz commented Dec 4, 2017

@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 bradfitz modified the milestones: Go1.10, Go1.11 Dec 4, 2017

@ivan4th

This comment has been minimized.

Show comment
Hide comment
@ivan4th

ivan4th Dec 4, 2017

@bradfitz to be more precise, it used to be enabled but didn't really work. It was then disabled in 343b4ba

ivan4th commented Dec 4, 2017

@bradfitz to be more precise, it used to be enabled but didn't really work. It was then disabled in 343b4ba

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

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).

Contributor

ianlancetaylor commented Dec 4, 2017

@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

This comment has been minimized.

Show comment
Hide comment
@hgfischer

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?

Contributor

hgfischer commented Dec 4, 2017

@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?

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

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.

Contributor

ianlancetaylor commented Dec 5, 2017

@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.

@nnnn20430

This comment has been minimized.

Show comment
Hide comment
@nnnn20430

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?
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.

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

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.

Contributor

ianlancetaylor commented Mar 20, 2018

@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

This comment has been minimized.

Show comment
Hide comment
@nnnn20430

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
comment

PPS: again it was late... now i see this was even said here before... #1435 (comment)

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

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.

Contributor

ianlancetaylor commented Mar 20, 2018

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.

@bradfitz bradfitz modified the milestones: Go1.11, Unplanned Jun 20, 2018

@bradfitz bradfitz added help wanted and removed Suggested labels Jun 20, 2018

wheatman added a commit to wheatman/go-akaros that referenced this issue Jun 25, 2018

syscall: disable Setuid/Setgid on linux
Update #1435

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://code.google.com/p/go/issues/detail?id=1435#c45

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment