Skip to content
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

Closed
gopherbot opened this issue Jan 21, 2011 · 97 comments
Closed

syscall: Setuid/Setgid doesn't apply to all threads on Linux #1435

gopherbot opened this issue Jan 21, 2011 · 97 comments

Comments

@gopherbot
Copy link

@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
Copy link
Author

@gopherbot 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
Copy link
Contributor

@alberts 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
Copy link
Contributor

@alberts 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
Copy link
Author

@gopherbot 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
Copy link
Author

@gopherbot 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
Copy link
Author

@gopherbot 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
Copy link
Contributor

@robpike robpike commented Jan 21, 2011

Comment 7:

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

Status changed to Accepted.

@rsc
Copy link
Contributor

@rsc 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
Copy link
Author

@gopherbot 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
Copy link
Author

@gopherbot 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
Copy link
Contributor

@rsc 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
Copy link
Author

@gopherbot 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
Copy link
Author

@gopherbot 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
Copy link
Contributor

@rsc 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
Copy link
Author

@gopherbot 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
Copy link
Contributor

@rsc rsc commented Jan 21, 2011

Comment 16:

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

@gopherbot 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
Copy link
Author

@gopherbot 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
Copy link
Contributor

@rsc 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
Copy link
Contributor

@alberts 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
Copy link
Contributor

@rsc rsc commented Dec 9, 2011

Comment 21:

Labels changed: added priority-later.

@gopherbot
Copy link
Author

@gopherbot 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
Copy link
Contributor

@rsc rsc commented May 15, 2012

Comment 24:

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

@rsc rsc commented Sep 12, 2012

Comment 25:

Labels changed: added go1.1.

@bpowers
Copy link
Contributor

@bpowers 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
Copy link
Contributor

@rsc rsc commented Dec 10, 2012

Comment 27:

Labels changed: added size-l.

@extemporalgenome
Copy link
Contributor

@extemporalgenome 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
Copy link
Contributor

@rsc 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
Copy link
Contributor

@rsc rsc commented Jul 30, 2013

Comment 30:

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

@robpike
Copy link
Contributor

@robpike robpike commented Aug 20, 2013

Comment 31:

Labels changed: removed go1.3maybe.

@larytet
Copy link

@larytet larytet commented Apr 2, 2019

The page is the top result in the google search. Thus I put an arguably verbose example of fork/exec here.

If do not like syscall.Syscall(syscall.SYS_SETUID, uintptr(uid), 0, 0) you can do this:

// Read environment variable USER, fork the process
func SwitchUser(daemon bool) (*user.User, error) {
	userCurrent, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("Failed to get current user %v", err)
	}

	userRequired := userCurrent
	if v := os.Getenv("USER"); v != "" {        // Rely on the environment
		userRequired, err = user.Lookup(v)  // and make it a truly a single liner in the calling code
		if err != nil {
			return userCurrent, fmt.Errorf("Failed to lookup '%s' %v", v, err)
		}
	}

	if userRequired.Uid == userCurrent.Uid {
		return userCurrent, nil
	}

	if uid := os.Getuid(); uid != 0 { // Straight from the exec unitest 
		return userCurrent, fmt.Errorf("I need root credentials, got %v instead", uid)
	}

	executable, err := filepath.Abs(filepath.Dir(os.Args[0]))  // Who am I? What is me?
	if err != nil {
		return userCurrent, fmt.Errorf("Failed to get path args[0]=%s %v", os.Args[0], err)
	}
	executable = path.Join(executable, path.Base(os.Args[0]))

	cmd := exec.Command(executable)
	gid, _ := strconv.Atoi(userRequired.Gid)    // this is the magic part 
	uid, _ := strconv.Atoi(userRequired.Uid)    // run the same executable 
	cmd.SysProcAttr = &syscall.SysProcAttr{  // with a different user
		Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
	}
	cmd.Stdout = os.Stdout  // you probably want to see the child's logs 
	cmd.Stderr = os.Stderr   // in the parent's stdout
	cmdRun := cmd.Run
	if daemon {
		cmdRun = cmd.Start
	}
	if err := cmdRun(); err != nil {
                // Or exec failed or the child failed 
		return userCurrent, fmt.Errorf("Failed to run %s as user %s %v", executable, userRequired.Name, err)
	} else {
                // Or daemon is true, or the child did Exit(0)
		return userRequired, nil
	}
}
buraksarac pushed a commit to qunixorg/libcap that referenced this issue Dec 5, 2019
The program web.go uses "libcap/cap" to raise and lower capabilities
in order to bind to a privileged port. Writing this code, I now
realize that Go's runtime is not really suited to minimal privilege
guarantees. The code does raise and lower the effective capability
Value needed, but to be fully robust, we're going to have to wait for
the following issue with the Go runtime to find a resolution:

  golang/go#1435

Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
@gopherbot
Copy link
Author

@gopherbot gopherbot commented Dec 10, 2019

Change https://golang.org/cl/210639 mentions this issue: syscall: Support POSIX semantics for Linux syscalls.

@forsyth
Copy link

@forsyth forsyth commented Dec 12, 2019

> // To listen on port 80 we need root privileges
ls, err := net.Listen("tcp", "127.0.0.1:80")
if err != nil {
    log.Exitln("Can't listen:", err)
}

You could instead do what I do, specifically to avoid having a Go program run even briefly as root, but still able to listen on 80 and 443: use CAP_NET_BIND_SERVICE=+eip. I use a command "bless", attached, to set the attribute each time the file is updated from my build server. The executable name is fixed, so bless itself can be setuid, but you could add an argument instead.
bless.c.txt

The < 1024 restriction is, of course, long obsolete. It wasn't even all that sensible when BSD had it for their timesharing systems "to make them more secure".

@jedisct1
Copy link

@jedisct1 jedisct1 commented Dec 12, 2019

@forsyth Unfortunately, this is a linux-only thing.

@forsyth
Copy link

@forsyth forsyth commented Dec 12, 2019

I hadn't noticed this item was so old and undoubtedly the original user's need has long since gone away. Sadly, the "privileged port" stuff, let alone root itself hasn't gone away in the 8 years since.

buraksarac pushed a commit to qunixorg/libcap that referenced this issue Dec 12, 2019
I've moved my go.patch to address:

  golang/go#1435

into a development patch against the upstream Go sources:

  https://go-review.googlesource.com/c/go/+/210639/

and the review process will likely evolve it somewhat. I plan to
ensure that working libcap/cap Go package is in sync with the
working state of the above development change.

As such, there is no need to keep the patch here any more.
I'll keep the tests for now, as it isn't clear to me how the Go
source tree supports tests that require privilege yet.

Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
@ibukanov
Copy link

@ibukanov ibukanov commented Dec 13, 2019

An alternative to CAP_NET_BIND_SERVICE=+eip is to give CAP_NET_BIND_SERVICE as ambient capability without marking the exe as such. This way only invocations via the service manager will be allowed to bind to the privileged port, not an arbitrary invocation. Systemd allows that with one line in the service file via AmbientCapabilities=. Or one can use setpriv utility.

@tv42
Copy link

@tv42 tv42 commented Dec 14, 2019

If you're using systemd, you can just let it pass an open listening fd in as it executes your app. Also nicely limits what ports your app can use, with complete admin control.

Maybe these low port workarounds should be marked as off-topic. They're not helping solve the actual problem; try reimplementing samba or opensshd in Go and you'll hit this limit pretty quickly.

@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Dec 29, 2019

New to this issue tracker. Happy to be assigned this issue. I have a pending change to fix it here:

https://go-review.googlesource.com/c/go/+/210639

@ianlancetaylor ianlancetaylor assigned AndrewGMorgan and unassigned rsc Dec 29, 2019
@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Dec 29, 2019

On the subject of capabilities and Go... I'd like to point out that the latest libcap-2.29 sources include a full port of libcap to Go ( see the official sources for libcap and release notes here: https://sites.google.com/site/fullycapable/ )

Using cgo and a C library libpsx for now, capability raising and lowering of all sets including the ambient one are fully supported in the Go package.

The native (CGO_ENABLED=0) build of the Go package requires the above https://go-review.googlesource.com/c/go/+/210639 patch to run. The libcap source tree includes a small webserver example:

https://git.kernel.org/pub/scm/libs/libcap/libcap.git/tree/go/web.go

-- update:

to add Go module build support, I've moved the web.go sources to:

https://git.kernel.org/pub/scm/libs/libcap/libcap.git/tree/goapps/web

@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Feb 23, 2020

I don't seem to be able to edit the tags associated with this bug. I'm not sure help is wanted for the code, but would appreciate any real world testing if folk want to stress test the proposed patch...

aaithal added a commit to aws/amazon-ecs-shim-loggers-for-containerd that referenced this issue Apr 6, 2020
Add uid and gid option in input arguments. Set all goroutines uids/gids by
the customized uid/gid. uid/gid=0 is is not supported.
Syscall of Setuid/Setgid in golang now does not apply to all threads on Linux,
see issue: golang/go#1435

We can remove the syscall in all the goroutines other than the main one once their
code change released: https://go-review.googlesource.com/c/go/+/210639

Following goroutines are set:
1. main goroutine
2. two sendLogs goroutine for blocking log driver
3. one sendLogs and two saveLogsToBuffer goroutines for non-blocking
   log driver.
@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Jul 5, 2020

FYI As a follow up to #1435 (comment) the latest two releases of libcap also contain Go module buildable versions of the libcap/cap and libcap/psx packages. I've done a write up of the web.go example (which I moved around to make it buildable with the module system) here:

https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities

This doesn't address the native Go build issue pending a solution in the form of https://go-review.googlesource.com/c/go/+/210639 patch, but it does allow general support for mirroring system calls in a combined Go/CGo runtime.

@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Jul 6, 2020

The modified instructions over that write up for validating and getting started exploring the above patch are to do this to build the web binary:

$ cd bar
$ git clone https://go.googlesource.com/go
$ cd go
$ git fetch https://go.googlesource.com/go refs/changes/39/210639/38 && git checkout -b change-210639 FETCH_HEAD
$ cd src
$ ./make.bash
$ cd ../..
$ mkdir foo
$ cd foo
$ wget https://git.kernel.org/pub/scm/libs/libcap/libcap.git/plain/goapps/web/web.go
$ ../go/bin/go mod init web
$ CGO_ENABLED=0 ../go/bin/go build -tags allthreadssyscall web.go
$ ldd web
        not a dynamic executable

You can follow the rest of https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities for how to use it.

@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Jul 9, 2020

I realize that the above is only just on topic for the initial subject of this bug ;) Here is an explanation of how to use the same packages to address the reason this bug was filed (currently via cgo, while the AllThreadsSyscall patch is still pending):

https://sites.google.com/site/fullycapable/getting-started-with-go/using-go-to-set-uid-and-gids

@AndrewGMorgan
Copy link
Contributor

@AndrewGMorgan AndrewGMorgan commented Oct 24, 2020

Note, one build target linux-ppc64 appears to fail the added tests for this feature. Resolving that is the subject of #42178.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.