How to apply setcap
before debugging Go application in VSCode in Linux?
I have an app that requires special capabilities. Normally to run application from shell I’d write something like
go build
sudo setcap 'cap_net_admin=+ep'./myapp
./myapp
But of course this allows to debug application only by using logs.
Is there a way to properly use delve
debugger from within VSCode? Can I apply capabilities on temporary file like __debug_bin3007192616
that is being generated by delve?
The problem you are facing is that you have an unprivileged program (dlv
) wanting to trace a privileged program (myapp
). Since such tracing can be used by dlv
to cause myapp
to run arbitrarily operations with privilege that dlv
doesn’t have, the kernel is preventing the ./myapp
process from running with that privilege.
You can see the same effect with the strace
tool.
// Program myapp is our test program (save as myapp.go)
package main
import (
"fmt"
"kernel.org/pub/linux/libs/security/libcap/cap"
)
func main() {
fmt.Println("started")
c := cap.GetProc()
fmt.Println("running with ", c)
}
Compile and run:
$ go get kernel.org/pub/linux/libs/security/libcap/cap@latest
$ go build myapp.go
$ ./myapp
started
running with =
$ sudo setcap 'cap_net_admin=ep' ./myapp
$ ./myapp
started
running with cap_net_admin=ep
$ strace ./myapp 2>&1 | grep write
write(1, "startedn", 8started
write(1, "running with =n", 16running with =
$
You can work around this by adding the privilege you want to the tracing program. In this case:
$ cp $(which strace) .
$ sudo setcap 'cap_net_admin=ep' ./strace
$ ./strace ./myapp 2>&1 | grep write
write(1, "startedn", 8started
write(1, "running with cap_net_admin=epn", 31running with cap_net_admin=ep
Now, because the tracer and tracee both have the same privilege the kernel is happy to allow the tracing to work.
Note, the myapp
program is running with the privilege it has on its file, and not inheriting from this version of ./strace
because capability inheritance doesn’t work that way. You can confirm this detail with:
$ ./strace /sbin/capsh --current 2>&1 | grep write
write(1, "Current: =nCurrent IAB: n", 25Current: =
All that being said, for your example, it means you need to run your dlv
binary with at least the privilege that ./myapp
has.
Because the kernel defaults to using a HYBRID privilege mode, you can (by default) grant your dlv
enough privilege through Ambient inheritance:
$ sudo capsh --user=$(whoami) --iab='^cap_net_admin' -- -c "~/go/bin/dlv exec ./myapp"
Type 'help' for list of commands.
(dlv) r
Process restarted with PID 12836
(dlv) c
started
running with cap_net_admin=eip
Process 12836 has exited with status 0
(dlv) exit
$
Note, again, the privilege (the e
and p
capabilities) of the actual ./myapp
runtime is coming from the file capability it has. The i
bits are coming from a side effect of the Ambient inheritance. You can confirm this with this command:
sudo capsh --user=$(whoami) --iab='^cap_net_admin,^cap_setuid' -- -c "~/go/bin/dlv exec ./myapp"
Type 'help' for list of commands.
(dlv) r
Process restarted with PID 12865
(dlv) c
started
running with cap_net_admin=eip cap_setuid+i
Process 12865 has exited with status 0
(dlv) exit
Again, the spurious i
capabilities are there via inheritance, but don’t actually affect the privileges of the running ./myapp
program.
2