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

Created on 21 Jan 2011  ·  99Comments  ·  Source: golang/go

by [email protected]:

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)
NeedsFix OS-Linux early-in-cycle help wanted

Most helpful comment

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

All 99 comments

Comment 1 by [email protected]:

Well, you just must just put the gorutine creation after the suid/sguid call.
Isn't that what you would actually expect?
...
    en = syscall.Setuid(uid)
    if en != 0 {
        fmt.Println("Setuid error:", os.Errno(en))
        os.Exit(1)
    }

    for ii := 1; ii < 10; ii++ {
        go printIds(ii)
        time.Sleep(1e8)
    }
    printIds(0)
sudo -i
export GOMAXPROCS=2
/tmp/setuid 65534 65534
gorutine 1: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 2: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 3: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 4: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 5: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 6: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 7: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 8: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 9: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 0: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 1: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 2: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 3: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 4: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 5: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 6: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 7: uid=65534 euid=65534 gid=65534 egid=65534
gorutine 8: uid=65534 euid=65534 gid=65534 egid=65534

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.

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.

Comment 4 by [email protected]:

You could use runtime.Goroutines() to wait if you are the only one.
If it isn't possible to terminate all "someHousekeepingFunc()" e.g.
with Goexit(), I think setuid gains you to security not much ...

Comment 5 by [email protected]:

Example code which shows how I discovered this problem:
package main
import (
    "os"
    "net"
    "http"
    "fmt"
    "syscall"
    "log"
)
type Handler string
func (hh *Handler) ServeHTTP(con http.ResponseWriter, req *http.Request) {
    fmt.Fprint(con, "Hello! I am %s. My UID/GID is %d/%d. Bye!\n", *hh,
        syscall.Getuid(), syscall.Getgid())
}
func main() {
    // To listen on port 80 we need root privileges
    ls, err := net.Listen("tcp", "127.0.0.1:80")
    if err != nil {
        log.Exitln("Can't listen:", err)
    }
    // We don't need root privileges any more
    if en := syscall.Setgid(65534); en != 0 {
        log.Exitln("Setgid error:", os.Errno(en))
    }
    if en := syscall.Setuid(65534); en != 0 {
        log.Exitln("Setuid error:", os.Errno(en))
    }
    // Run http service without root privileges
    handler := Handler("Test handler")
    if err = http.Serve(ls, &handler); err != nil {
        log.Exitln("Http server:", err)
    }
}
Compile it and run as root. Then use ps -efL to show threads:
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
nobody   32401 32333 32401  0    2 18:48 pts/2    00:00:00 ./http
root     32401 32333 32402  0    2 18:48 pts/2    00:00:00 ./http
There is two threads. But I didn't create any gorutine explicitly. Use curl to send
request to the application:
$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/65534. Bye!
Now ps shows:
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
nobody   32401 32333 32401  0    3 18:48 pts/2    00:00:00 ./http
root     32401 32333 32402  0    3 18:48 pts/2    00:00:00 ./http
nobody   32401 32333 32406  0    3 18:49 pts/2    00:00:00 ./http
Use siege stress test:
$ siege 127.0.0.1 -c25 -d0 -t 10s
** SIEGE 2.70
** Preparing 25 concurrent users for battle.
The server is now under siege...
Lifting the server siege..      done.                                                   
 Transactions:             12543 hits
Availability:             100.00 %
Elapsed time:               9.99 secs
Data transferred:           0.65 MB
Response time:              0.02 secs
Transaction rate:        1255.56 trans/sec
Throughput:             0.06 MB/sec
Concurrency:               24.79
Successful transactions:       12544
Failed transactions:               0
Longest transaction:            0.04
Shortest transaction:           0.01

Now ps shows:
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
nobody   32401 32333 32401  0   11 18:48 pts/2    00:00:01 ./http
root     32401 32333 32402  0   11 18:48 pts/2    00:00:01 ./http
nobody   32401 32333 32406  0   11 18:49 pts/2    00:00:01 ./http
nobody   32401 32333 32553  0   11 18:51 pts/2    00:00:00 ./http
nobody   32401 32333 32554  2   11 18:51 pts/2    00:00:01 ./http
root     32401 32333 32555  3   11 18:51 pts/2    00:00:01 ./http
root     32401 32333 32556  3   11 18:51 pts/2    00:00:01 ./http
root     32401 32333 32557  0   11 18:51 pts/2    00:00:00 ./http
root     32401 32333 32558  3   11 18:51 pts/2    00:00:01 ./http
nobody   32401 32333 32559  3   11 18:51 pts/2    00:00:01 ./http
nobody   32401 32333 32560  3   11 18:51 pts/2    00:00:01 ./http
Use curl a few times:
$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/0. Bye!
$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/0. Bye!
$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/0. Bye!
It's nice! I talking with root on your server!

Comment 6 by [email protected]:

Oh, you are right. There is currently no official way to use a net socket without
spawning 2nd goroutine (EpollWait):
goroutine 2 [3]:
runtime.entersyscall+0x28 /data4/soft/go/go/src/pkg/runtime/proc.c:577
    runtime.entersyscall()
syscall.Syscall6+0x5 /data4/soft/go/go/src/pkg/syscall/asm_linux_amd64.s:40
    syscall.Syscall6()
syscall.EpollWait+0x8d /data4/soft/go/go/src/pkg/syscall/zsyscall_linux_amd64.go:188
    syscall.EpollWait(0x7fda00000006, 0x7fda62d566a0, 0x100000001, 0xffffffff, 0xc, ...)
net.*pollster·WaitFD+0xfe /data4/soft/go/go/src/pkg/net/fd_linux.go:116
    net.*pollster·WaitFD(0x7fda62d564d0, 0x0, 0x0, 0x0, 0x0, ...)
net.*pollServer·Run+0xa3 /data4/soft/go/go/src/pkg/net/fd.go:207
    net.*pollServer·Run(0x7fda62d20600, 0x0)
runtime.goexit /data4/soft/go/go/src/pkg/runtime/proc.c:149
    runtime.goexit()
but opening a file descriptor works:
f, err := os.Open("/etc/shadow", os.O_RDONLY | os.O_SYNC , 0666)
fmt.Println(runtime.Goroutines())
... syscall.Setuid(uid) ... syscall.Setgid(gid)
var buf [20]byte
_, err = f.Read(buf[:]);
fmt.Println(buf)
time.Sleep(100e9)
./run
open /etc/shadow: permission denied
sudo ./run
1
[114 111 111 ...
ps -efL|grep run
mc       14097 24745 14097  0    1 19:36 pts/4    00:00:00 /run 1001 1001

Comment 7:

_Owner changed to [email protected]._

_Status changed to Accepted._

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

Comment 9 by [email protected]:

If I add runtime.LockOSThread in my second example, just before Setgid, I get strange
behavior:
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/0. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/65534. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/0. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/65534. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/65534. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/0. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/65534. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/0. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 65534/65534. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/0. Bye!
michal@md-lap:~$ curl 127.0.0.1
Hello! I am Test handler. My UID/GID is 0/65534. Bye!

Attachments:

  1. http.go (969 bytes)

Comment 10 by [email protected]:

Sorry. I had not noticed it before, but this is also when there is no
runtime.LockOSThread call.
This is probably due to rescheduling between syscall.Getuid() and syscall.Getgid().

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

Comment 12 by [email protected]:

I probably found workaround for my web application:
package main
import (
    "os"
    "net"
    "http"
    "fmt"
    "syscall"
    "runtime"
    "log"
)
func lockUidGid(new_uid, new_gid int) {
    runtime.LockOSThread()
    uid := syscall.Getuid()
    gid := syscall.Getgid()
    if  uid == new_uid && gid == new_gid {
        return
    }
    if en := syscall.Setgid(new_uid); en != 0 {
        log.Exitln("Setgid error:", os.Errno(en))
    }
    if en := syscall.Setuid(new_gid); en != 0 {
        log.Exitln("Setuid error:", os.Errno(en))
    }
}
type Handler string
func (hh *Handler) ServeHTTP(con http.ResponseWriter, req *http.Request) {
    lockUidGid(65534, 65534)
    fmt.Fprintf(con, "Hello! I am %s. My UID/GID is %d/%d. Bye!\n", *hh,
        syscall.Getuid(), syscall.Getgid())
}
func main() {
    // To listen on port 80 we need root privileges
    ls, err := net.Listen("tcp", "127.0.0.1:80")
    if err != nil {
        log.Exitln("Can't listen:", err)
    }
    // We don't need root privileges any more
    lockUidGid(65534, 65534)
    // Run http service without root privileges
    handler := Handler("Test handler")
    if err = http.Serve(ls, &handler); err != nil {
        log.Exitln("Http server:", err)
    }
}
After running siege there is no threads with root privileges:
$ siege 127.0.0.1 -c25 -d0 -t10s
** SIEGE 2.69
** Preparing 25 concurrent users for battle.
The server is now under siege...
Lifting the server siege..      done.
Transactions:              28782 hits
Availability:             100.00 %
Elapsed time:               9.79 secs
Data transferred:           1.59 MB
Response time:              0.01 secs
Transaction rate:        2939.94 trans/sec
Throughput:             0.16 MB/sec
Concurrency:               24.86
Successful transactions:       28782
Failed transactions:               0
Longest transaction:            0.04
Shortest transaction:           0.00
$ ps -efL|egrep 'http|UID'|egrep -v egrep
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
nobody    4109  2928  4109  1   10 23:00 pts/1    00:00:04 ./http
nobody    4109  2928  4110  0   10 23:00 pts/1    00:00:02 ./http
nobody    4109  2928  4112  0   10 23:00 pts/1    00:00:02 ./http
nobody    4109  2928  4153  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4154  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4155  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4156  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4157  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4158  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4159  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4109  1   10 23:00 pts/1    00:00:04 ./http
nobody    4109  2928  4110  0   10 23:00 pts/1    00:00:02 ./http
nobody    4109  2928  4112  0   10 23:00 pts/1    00:00:02 ./http
nobody    4109  2928  4153  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4154  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4155  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4156  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4157  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4158  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4159  0   10 23:01 pts/1    00:00:01 ./http
Thanks!

Comment 13 by [email protected]:

I probably found workaround for my web application:
package main
import (
    "os"
    "net"
    "http"
    "fmt"
    "syscall"
    "runtime"
    "log"
)
func lockUidGid(new_uid, new_gid int) {
    runtime.LockOSThread()
    uid := syscall.Getuid()
    gid := syscall.Getgid()
    if  uid == new_uid && gid == new_gid {
        return
    }
    if en := syscall.Setgid(new_uid); en != 0 {
        log.Exitln("Setgid error:", os.Errno(en))
    }
    if en := syscall.Setuid(new_gid); en != 0 {
        log.Exitln("Setuid error:", os.Errno(en))
    }
}
type Handler string
func (hh *Handler) ServeHTTP(con http.ResponseWriter, req *http.Request) {
    lockUidGid(65534, 65534)
    fmt.Fprintf(con, "Hello! I am %s. My UID/GID is %d/%d. Bye!\n", *hh,
        syscall.Getuid(), syscall.Getgid())
}
func main() {
    // To listen on port 80 we need root privileges
    ls, err := net.Listen("tcp", "127.0.0.1:80")
    if err != nil {
        log.Exitln("Can't listen:", err)
    }
    // We don't need root privileges any more
    lockUidGid(65534, 65534)
    // Run http service without root privileges
    handler := Handler("Test handler")
    if err = http.Serve(ls, &handler); err != nil {
        log.Exitln("Http server:", err)
    }
}
After running siege there is no threads with root privileges:
$ siege 127.0.0.1 -c25 -d0 -t10s
** SIEGE 2.69
** Preparing 25 concurrent users for battle.
The server is now under siege...
Lifting the server siege..      done.
Transactions:                  28782 hits
Availability:                 100.00 %
Elapsed time:                   9.79 secs
Data transferred:               1.59 MB
Response time:                  0.01 secs
Transaction rate:            2939.94 trans/sec
Throughput:                     0.16 MB/sec
Concurrency:                   24.86
Successful transactions:       28782
Failed transactions:               0
Longest transaction:            0.04
Shortest transaction:           0.00
$ ps -efL|egrep 'http|UID'|egrep -v egrep
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
nobody    4109  2928  4109  1   10 23:00 pts/1    00:00:04 ./http
nobody    4109  2928  4110  0   10 23:00 pts/1    00:00:02 ./http
nobody    4109  2928  4112  0   10 23:00 pts/1    00:00:02 ./http
nobody    4109  2928  4153  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4154  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4155  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4156  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4157  0   10 23:01 pts/1    00:00:00 ./http
nobody    4109  2928  4158  0   10 23:01 pts/1    00:00:01 ./http
nobody    4109  2928  4159  0   10 23:01 pts/1    00:00:01 ./http
Thanks!

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

Comment 15 by [email protected]:

>For now you can work around this by calling runtime.LockOSThread.
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
ls, _ := net.Listen("tcp", "localhost:42")
syscall.Setuid(uid)
http.Serve(ls, nil)
Was that what you meant? It's actually impossible ;)
There is always +1 thread spawned by Listen that keeps
running as root.
ps -efL| grep setuid
mc       19213 24745 19213  0    2 22:41 pts/4    00:00:00 /tmp/setuid 1001 1001
root     19213 24745 19215  0    2 22:41 pts/4    00:00:00 /tmp/setuid 1001 1001
^\SIGQUIT: quit
PC=0x411b25
runtime.futex+0x23 /data4/soft/go/go/src/pkg/runtime/linux/amd64/sys.s:137
    runtime.futex()
futexsleep+0x50 /data4/soft/go/go/src/pkg/runtime/linux/thread.c:51
    futexsleep(0x664c18, 0x300000003, 0x0, 0x0)
futexlock+0x85 /data4/soft/go/go/src/pkg/runtime/linux/thread.c:119
    futexlock(0x664c18, 0x100000000)
runtime.notesleep+0x25 /data4/soft/go/go/src/pkg/runtime/linux/thread.c:204
    runtime.notesleep(0x664c18, 0x7fffa0812498)
nextgandunlock+0x146 /data4/soft/go/go/src/pkg/runtime/proc.c:343
    nextgandunlock()
scheduler+0x16f /data4/soft/go/go/src/pkg/runtime/proc.c:536
    scheduler()
runtime.mstart+0x74 /data4/soft/go/go/src/pkg/runtime/proc.c:393
    runtime.mstart()
_rt0_amd64+0x95 /data4/soft/go/go/src/pkg/runtime/amd64/asm.s:69
    _rt0_amd64()
goroutine 2 [3]:
runtime.entersyscall+0x28 /data4/soft/go/go/src/pkg/runtime/proc.c:577
    runtime.entersyscall()
syscall.Syscall6+0x5 /data4/soft/go/go/src/pkg/syscall/asm_linux_amd64.s:40
    syscall.Syscall6()
syscall.EpollWait+0x8d /data4/soft/go/go/src/pkg/syscall/zsyscall_linux_amd64.go:188
    syscall.EpollWait(0x7f6600000006, 0x7f669eb58950, 0x100000001, 0xffffffff, 0xc, ...)
net.*pollster·WaitFD+0xfe /data4/soft/go/go/src/pkg/net/fd_linux.go:116
    net.*pollster·WaitFD(0x7f669eb587a0, 0x0, 0x6400000000, 0x0, 0x0, ...)
net.*pollServer·Run+0xa3 /data4/soft/go/go/src/pkg/net/fd.go:207
    net.*pollServer·Run(0x7f669eb27600, 0x0)
runtime.goexit /data4/soft/go/go/src/pkg/runtime/proc.c:149
    runtime.goexit()
goroutine 1 [4]:
runtime.gosched+0x77 /data4/soft/go/go/src/pkg/runtime/proc.c:558
    runtime.gosched()
runtime.chanrecv+0x18b /data4/soft/go/go/src/pkg/runtime/chan.c:364
    runtime.chanrecv(0x7f669eb3eea0, 0x7f669eb13da8, 0x0, 0x0, 0x0, ...)
runtime.chanrecv1+0x41 /data4/soft/go/go/src/pkg/runtime/chan.c:444
    runtime.chanrecv1(0x7f669eb3eea0, 0x7f669eb123c0)
net.*pollServer·WaitRead+0x52 /data4/soft/go/go/src/pkg/net/fd.go:247
    net.*pollServer·WaitRead(0x7f669eb27600, 0x7f669eb123c0, 0x0, 0x0)
net.*netFD·accept+0x39a /data4/soft/go/go/src/pkg/net/fd.go:579
    net.*netFD·accept(0x7f669eb123c0, 0x43efe8, 0x0, 0x0, 0x0, ...)
net.*TCPListener·AcceptTCP+0x71 /data4/soft/go/go/src/pkg/net/tcpsock.go:261
    net.*TCPListener·AcceptTCP(0x7f669eb0f178, 0x7f669eb13ee0, 0x0, 0x0, 0x1007f6600000001, ...)
net.*TCPListener·Accept+0x49 /data4/soft/go/go/src/pkg/net/tcpsock.go:271
    net.*TCPListener·Accept(0x7f669eb0f178, 0x0, 0x0, 0x0, 0x0, ...)
http.Serve+0x7a /data4/soft/go/go/src/pkg/http/server.go:665
    http.Serve(0x7f669eb27b40, 0x7f669eb0f178, 0x7f669eb4b030, 0x7f669eb0f0a8, 0x0, ...)
main.main+0x886 /home/mc/server/go/tests/setuid2.go:73
    main.main()
runtime.mainstart+0xf /data4/soft/go/go/src/pkg/runtime/amd64/asm.s:77
    runtime.mainstart()
runtime.goexit /data4/soft/go/go/src/pkg/runtime/proc.c:149
    runtime.goexit()
rax     0xfffffffffffffffc
rbx     0x664c18
rcx     0xffffffffffffffff
rdx     0x3
rdi     0x664c18
rsi     0x0
rbp     0x7f669eb13c98
rsp     0x7fffa08123e8
r8      0x0
r9      0x0
r10     0x6601b0
r11     0x206
r12     0x250
r13     0x7fffa0812500
r14     0x0
r15     0x0
rip     0x411b25
rflags  0x206
cs      0x33
fs      0x0
gs      0x0
Trace/breakpoint trap

Comment 16:

Yes, the LockOSThread workaround only applies to one goroutine.

Comment 17 by [email protected]:

Thanks, I didn't hit the reload button and missed the comments.
BTW, the crock design is elaborated here ;)
http://www.cs.utexas.edu/~witchel/372/lectures/POSIX_Linux_Threading.pdf

Comment 18 by [email protected]:

I suggest put a clear warning in the header of syscall package about issues that may
cause the use of this package.
Maybe a list of links to known issues for each function would not be a bad idea...

Comment 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

Comment 20:

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

Comment 21:

_Labels changed: added priority-later._

Comment 23 by [email protected]:

Is anyone working on that? If there are some experimental patches I would be happy to
test them ;-)
I added a little test case to my own little library to detect non-posix compliant
systems (all the *bsd variants work as expected): https://github.com/sarnowski/mitigation

Comment 24:

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

Comment 25:

_Labels changed: added go1.1._

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)

Comment 27:

_Labels changed: added size-l._

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.

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

Comment 30:

_Labels changed: added go1.3maybe, removed go1.2._

Comment 31:

_Labels changed: removed go1.3maybe._

Comment 32:

_Labels changed: added go1.3maybe._

Comment 33:

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

Comment 34:

_Labels changed: added repo-main._

Comment 35 by [email protected]:

If it is impossible to get a fix in for this for 1.3, could we at least get a
documentation fix in (at syscall.Setuid et al.)? It would have saved me quite a number
of hours. If so I can submit the change. I'd also be interested at trying to make the
fix if it's not too late for 1.3.

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.

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.

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

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?

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

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.
[email protected]: could you elaborate on the setcap solution?

Comment 42:

listening on privilege ports on linux needs not the full root, but only
a single capabilities(8) called CAP_NET_BIND_SERVICE.
you can grant any capability on any executable file by using the setcap(8)
command.
e.g.:
sudo setcap cap_net_bind_service=+ep go_server
and then you can run go_server as a normal user (e.g. nobody).

Comment 43 by peter.waller:

Aha! I was expecting a syscall, not an extended file attribute, which is why I was
confused when I looked at the setcap page. Awesome, thanks minux.

Comment 44:

> With all due respect, but if you don't consider GNU/Linux to be an important web
server platform, I don't know what is.
I don't know what you mean by that.  Of course GNU/Linux is an important web server
platform.
> At the same time, how hard is it to do what requires "root" permission and the lower
it to a lower permission user?
On GNU/Linux, it's best to use the setcap mechanism, and not use root permission at all.
 setcap has been around since Linux 2.2, it's not some newfangled thing.
That said, we all agree that this bug should be fixed.  It would be great if you worked
on fixing it, rather than simply telling us that it is easy to fix.  Thanks.

Comment 45 by StevensElectronicMail:

Given that setuid is broken with Go can it be removed (or maybe somehow tagged with a
warning) while we're waiting for a fix so that nobody uses it by accident. I know this
would break backwards compatibility but in this case it SHOULD break backwards
compatibility because code that uses it most probably has a security hole.

Comment 46:

I don't know about removing the symbol, but I think there is an argument to be made that
it should _always_ return an error on systems where it is known to be faulty.

Comment 47:

SGTM

Comment 48:

CL https://golang.org/cl/106170043 mentions this issue.

Comment 49:

This issue was updated by revision 343b4ba8c1ad8a29b6dd19cb101273b57a26c9b.

This proposal disables Setuid and Setgid on all linux platforms.
issue #1435 has been open for a long time, and it is unlikely to be addressed soon so an
argument was made by a commenter
https://golang.org/issue/1435?c=45
That these functions should made to fail rather than succeed in their broken state.
LGTM=ruiu, iant
R=iant, ruiu
CC=golang-codereviews
https://golang.org/cl/106170043

Comment 50:

Can we fix this before Go 1.4 by any chance? I read the glibc code briefly. It looks
like we need to (1) set a signal handler for SIGSETXID that calls setuid/setgid system
call from the handler, and (2) signal all threads with SIGSETXID when setuid/setgid Go
function is called. Because we don't provide syscall.sigaction, I think we don't need to
worry about the case that a user accidentally disable SIGSETXID system call. Is there
anything other than that we need to do for this?

Comment 51:

Since Setuid is used for security, and is often used to drop privileges, it's fairly
important to get it right.
In a pure Go program with no cgo, I believe we can freeze the world and force each
thread to use the system call.  I don't think we need any signals in that case.
The problem is a Go program that uses cgo.  We have to assume that cgo code has created
threads that we don't know about.  We also have to assume that cgo code can call setuid.
 Because of that possibility, I don't think we can provide a SIGSETXID signal handler
ourselves for a cgo program; see issue #3871.
Adding this all together, it now occurs to me that it may work to make syscall.Setuid a
function that uses cgo to call the C setuid function.  That will force the use of cgo
for any program that uses syscall.Setuid, meaning that every thread will be created
using pthread_create and that we should get the C library signal handler for SIGSETXID.
Want to see if that will work?

Comment 52:

@iant I'm assuming this approach could fix Unshare too?
https://golang.org/issue/1954

Comment 53:

Calling the C library version of unshare wouldn't make any difference.  The unshare
function in the GNU/Linux libc only applies to the current thread.  Since goroutines
aren't attached to a specific thread, there is no meaningful way to call unshare from a
goroutine.  We would have to introduce a mechanism to call unshare from every thread,
which is more or less what we are trying to avoid, and which is in any case impossible
if cgo code has created new threads.
I think the only way to use unshare from a Go program is to use runtime.LockOSThread. 
Or to arrange to call it just before a fork.  I don't think there is any reasonable way
to unshare across the whole process, and the same is true of C code.

Comment 54:

How do you force the use of cgo for any program that uses syscall.Setuid? The only way I
know how to do this would be to force the use of cgo for any program that uses syscall,
but that's untenable.
What we do in the runtime is check to see if cgo is already being used for some other
reason, and if so we use it for things like pthread_create but otherwise fall back to
our own implementation. I think we'd have to do something similar to play nicely with
cgo here.

Comment 55:

Argh, you're right.

Does setreuid apply to all threads? Would this be a work around when trying to use it to temporarily go to root?

All the related system calls apply only to a single thread. The full set is setuid setgid setgroups setreuid setregid setresuid setresgid (and the xxx32 variants were applicable).

Any updates on this ?

Thanks

No.

I would like to add that the suggested workaround of using capabilities does not work. In order to be useful for security, capabilities need to be dropped by the process after performing the privileged function. However just as you cannot setuid() other threads than your own, you cannot drop capabilities of other threads than your own. So the other threads created by the Go runtime keep the elevated privileges granted by the capabilities.

This is a big problem. E.g. the capability CAP_SYS_CHROOT is worthless if you cannot drop it, since that capability can be used to break out of chroots.

So even with the best effort using SystemD and file capabilities it is currently not possible to create a Go program that performs some privileged task and then drops ALL of its privileges. This is embarassing.

A better solution is for Go to provide a way to serve on an already-open socket FD. A launcher (written in C) could then open the socket, drop privileges, and exec() the Go program.

This could use a lot of tidying, but as a proof of concept: http://play.golang.org/p/dXBizm4xl3

When run as root, the program will open a privileged socket and re-exec itself as an unprivileged user passing down the opened FD.

% go build drop.go
% sudo ./drop
Dropping privileges...
Spawned process 30278, exiting
% curl 127.0.0.1
I am process 30278 running as 65534/65534

@bgilmore Thank you, that's just what I was about to start coding up. Really glad to see it works! (Can't believe I didn't think of this earlier...) Although it would be nice to do it in-process, this effectively solves the issue for me.

This trick should be documented as the way to drop priviliges in Go.
On Jan 21, 2016 11:46 PM, "Matt Holt" [email protected] wrote:

@bgilmore https://github.com/bgilmore Thank you, that's just what I was
about to start coding up. Really glad to see it works! (Can't believe I
didn't think of this earlier...) Although it would be nice to do it
in-process, this effectively solves the issue for me.


Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/1435#issuecomment-173805699.

Unfortunately, this does not work if you chroot, which is another one of the most important security measures together with dropping privileges. E.g. if you implement a service like Dropbox or Google Drive where naturally users have the ability to upload arbitrary files, it's a very good idea to chroot into a filesystem mounted no-exec and together with setuid this will protect you against so many common programming errors.

I'd suggest the GOOS=linux behavior for Setuid/Setgid is correct for all operating systems and should be extended to Chroot for all OS's. I don't think any user code should be able to change the process state of the runtime in such a way other user code might not be able to do something it was able to do before the process state of the runtime was changed. For example any package that relies on any kind of file access needs to assume the state of the runtime will be reasonably consistent and shouldn't have to assume something can change the effective root of the runtime at any time without notice. It should be pretty reasonable to assume a package can create a temporary file and write to it at any time without worrying some other user code could at any time change uid/gid and make it unwritable.

Using setuid/gid to "drop privileges" in a Go program would tend to give a false sense of security anyway, the obvious place to do it would be a main package's init() and that's the last init() to run if there's multiple, and would always lead to the case above where things were possible for an imported package's init() that aren't after main's init(). Setuid, Setgid and Chroot just introduce potential opaque, hard to debug chaos and provide little or no value.

Setuid, Setgid and Chroot just introduce potential opaque, hard to debug chaos and provide little or no value.

I beg to differ about the "little or no value" part. I'd like to see something like openbsds pledge eventually, though I realize that's probably not something that will happen anytime soon.

I think Go shouldn't schedule goroutines from a locked thread on another thread. goroutines are often used for control flow, and introducing a goroutine wouldn't be a "breaking change" in many cases in code you depend on... so it's hard to use Go when you really need a thread to be locked (e.g. when managing network namespaces).

Not sure whether #20676 will be of much help here since the problem is that on Linux setuid / setgid only affect the current thread instead of the entire process. Usually the goal of these calls is to drop/change privileges for the entire process, not just temporarily affect the privileges of the current thread.

One method of fixing this correctly would be a mirror to runtime.systemstack; e.g. runtime.everyM.

// Call Setuid for every OS thread in the process.
func Setuid(uid int) error {
    errCh := make(chan syscall.Errno)
    runtime.everyM(func() {
        _, _, err := syscall.Syscall(syscall.SYS_SETUID, uid, 0, 0)
        if err != nil {
            errCh <- err
        }
    })

    select {
    case err := <-errCh:
        return err
    default:
        return nil
    }
}

// and a Windows example...

// Call SetThreadToken for every OS thread of the process.
func SwitchToken(tokenHandle syscall.Handle) error {
    errCh := make(chan syscall.Errno)
    runtime.everyM(func() {
        r1, _, e1 := syscall.Syscall(procSetThreadToken.Addr(), 0, uintptr(tokenHandle), 0)
        if r1 == 0 {
            if e1 != 0 {
                errCh <- errnoErr(e1)
            } else {
                errCh <- syscall.EINVAL
            }
        }
    })
    select {
    case err := <-errCh:
        return err
    default:
        return nil
    }
}

The semantics of what happens when a machine thread is already in a syscall are currently unspecified, but sending a reserved signal like glibc does as a trigger to check for everyM tasks seems reasonable. Syscalls are already restarted on EINTR.

Will Setuid/Setgid someday be allowed again on Linux?
I have a use-case for it and I don't even bother that it just works in the main thread.

@hgfischer, what do you mean by "again"? I don't think this ever worked in Go.

In any case, bumping to Go 1.11 because this didn't happen for Go 1.10.

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

@hgfischer If you just want to change the ID of one thread (but why?) you can use runtime.LockOSThread and syscall.Syscall(syscall.SYS_SETUID, uid, 0, 0).

@bradfitz Thanks. As @ivan4th mentioned, it used to do something, some time ago, but then it got disabled because of this issue.

@ianlancetaylor Thanks for the tips. I tried just syscall.Syscall() before but it was not working for some reason. Since you mentioned, I tried a bit more and _voilá_!

The reason is to have a binary with setuid bit, that can be called by an unpriviledged user. I don't need goroutines for this, and the tool will be short lived. My test is here.
Could I still face the problems reported in this issue, in this use case?

@hgfischer Yes. Go programs are always multi-threaded, goroutines can migrate between threads at any function call, and the old syscall.Setuid only changed one thread in the program.

Couldn't this be solved by adding setuid/setguid parameters to syscall.SysProcAttr, and then have it call setuid/setgid in a thread safe way right before exec() when starting a new process?
So that it can at least set uid of a new process, and then you can just re-execute the program and have the original instance exit, successfully achieving privilege drop/escalation.

@nnnn20430 There is no particular problem with starting another program using a new UID/GID. We already support that via SysProcAttr.Credential.

The problem is with programs that want to change their effective uid/gid while executing. This is a more or less reasonable thing for a Unix program to do, but it doesn't currently work for Go programs on GNU/Linux. That is what this issue is about.

@ianlancetaylor ah oops, i just looked at surface level entries in the type didn't see setuid and assumed it to not be there, i was just thinking "can't this be solved like this?", and just skimmed over the type to confirm if it was there or not before commenting, sorry that was dumb....

PS: basically it was late and i wanted to comment before i forget and go to sleep
comment

PPS: again it was late... now i see this was even said here before... https://github.com/golang/go/issues/1435#issuecomment-66054177

I want to note that I think that any fix for this issue has to be quite different in pure Go programs and in programs that use cgo. In cgo-using programs I think we need to somehow use the glibc signal handler for SIGSETXID (SIGRTMIN + 1) and use the glibc call for setuid, setgid, setresgid, setgroups, setegid, setregid, setresuid, seteuid, setreuid, etc.

In pure Go programs we might be able to get away with queuing up a list of changes for each thread, and having the thread apply them every time it acquires a lock. My thinking here is that it's OK if the thread only reflects the UID change after there is some happens-before relationship between the UID call and the thread execution, and that should be mediated by a lock.

So is it safe to call glibc's version of setuid (which implies using CGO) just to setup the UID for all OS threads within the process? I've tested that and it seems to work but don't know if it will have any impact on the correctness of goroutine scheduling?

@ironsteel I can't think of a reason that that wouldn't work, but I also wouldn't be surprised if there were cases where it failed. One particular case to consider is if a new thread is created during the setuid call.

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

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

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

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

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

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

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

    cmd := exec.Command(executable)
    gid, _ := strconv.Atoi(userRequired.Gid)    // this is the magic part 
    uid, _ := strconv.Atoi(userRequired.Uid)    // run the same executable 
    cmd.SysProcAttr = &syscall.SysProcAttr{  // with a different user
        Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
    }
    cmd.Stdout = os.Stdout  // you probably want to see the child's logs 
    cmd.Stderr = os.Stderr   // in the parent's stdout
    cmdRun := cmd.Run
    if daemon {
        cmdRun = cmd.Start
    }
    if err := cmdRun(); err != nil {
                // Or exec failed or the child failed 
        return userCurrent, fmt.Errorf("Failed to run %s as user %s %v", executable, userRequired.Name, err)
    } else {
                // Or daemon is true, or the child did Exit(0)
        return userRequired, nil
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-- update:

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

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

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

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

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.

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

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

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

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

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

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

Happy to see this is fixed, but when will this be "backported", if at all? What Go version will this fix occur in?

This change is currently included in the 1.16 development branch. No currently released version of Go contains it.

Was this page helpful?
0 / 5 - 0 ratings