Skip to content

Commit e37a9a0

Browse files
committed
linux, exec: adds capabilities detection - prints a message when running pyroscope exec with no proper capabilities
1 parent 30ec4c0 commit e37a9a0

File tree

4 files changed

+110
-21
lines changed

4 files changed

+110
-21
lines changed

pkg/exec/cli.go

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os/exec"
88
"os/user"
99
"path"
10-
"runtime"
1110
"strconv"
1211
"strings"
1312
"syscall"
@@ -37,13 +36,12 @@ func Cli(cfg *config.Config, args []string) error {
3736
supportedSpies := spy.SupportedExecSpies()
3837
suggestedCommand := fmt.Sprintf("pyroscope exec -spy-name %s %s", supportedSpies[0], strings.Join(args, " "))
3938
return fmt.Errorf(
40-
"could not automatically find a spy for program \"%s\". Pass spy name via %s argument, for example: \n %s\n\nAvailable spies are: %s\n%s\nIf you believe this is a mistake, please submit an issue at %s",
39+
"could not automatically find a spy for program \"%s\". Pass spy name via %s argument, for example: \n %s\n\nAvailable spies are: %s\nIf you believe this is a mistake, please submit an issue at %s",
4140
baseName,
4241
color.YellowString("-spy-name"),
4342
color.YellowString(suggestedCommand),
4443
strings.Join(supportedSpies, ","),
45-
armMessage(),
46-
color.BlueString("https://github.com/pyroscope-io/pyroscope/issues"),
44+
color.GreenString("https://github.com/pyroscope-io/pyroscope/issues"),
4745
)
4846
}
4947
}
@@ -57,7 +55,7 @@ func Cli(cfg *config.Config, args []string) error {
5755
if cfg.Exec.ApplicationName == "" {
5856
logrus.Infof("we recommend specifying application name via %s flag or env variable %s", color.YellowString("-application-name"), color.YellowString("PYROSCOPE_APPLICATION_NAME"))
5957
cfg.Exec.ApplicationName = spyName + "." + names.GetRandomName(generateSeed(args))
60-
logrus.Infof("for now we chose the name for you and it's \"%s\"", color.BlueString(cfg.Exec.ApplicationName))
58+
logrus.Infof("for now we chose the name for you and it's \"%s\"", color.GreenString(cfg.Exec.ApplicationName))
6159
}
6260

6361
logrus.WithFields(logrus.Fields{
@@ -143,22 +141,17 @@ func waitForProcessToExit(cmd *exec.Cmd) {
143141

144142
func performChecks(spyName string) error {
145143
if spyName == "gospy" {
146-
return fmt.Errorf("gospy can not profile other processes. See our documentation on using gospy: %s", color.BlueString("https://pyroscope.io/docs/"))
144+
return fmt.Errorf("gospy can not profile other processes. See our documentation on using gospy: %s", color.GreenString("https://pyroscope.io/docs/"))
147145
}
148146

149-
if runtime.GOOS == "darwin" {
150-
if !isRoot() {
151-
logrus.Fatal("on macOS you're required to run the agent with sudo")
152-
}
153-
}
147+
performOSChecks()
154148

155149
if !stringsContains(spy.SupportedSpies, spyName) {
156150
supportedSpies := spy.SupportedExecSpies()
157151
return fmt.Errorf(
158-
"Spy \"%s\" is not supported. Available spies are: %s\n%s",
159-
color.BlueString(spyName),
152+
"Spy \"%s\" is not supported. Available spies are: %s\n",
153+
color.GreenString(spyName),
160154
strings.Join(supportedSpies, ","),
161-
armMessage(),
162155
)
163156
}
164157

@@ -179,13 +172,6 @@ func isRoot() bool {
179172
return err == nil && u.Username == "root"
180173
}
181174

182-
func armMessage() string {
183-
if runtime.GOARCH == "arm64" {
184-
return "Note that rbspy is not available on arm64 platform"
185-
}
186-
return ""
187-
}
188-
189175
func generateSeed(args []string) string {
190176
path, err := os.Getwd()
191177
if err != nil {

pkg/exec/cli_darwin.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// +build darwin
2+
3+
package exec
4+
5+
import (
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
func performOSChecks() {
10+
if !isRoot() {
11+
logrus.Fatal("on macOS you're required to run the agent with sudo")
12+
}
13+
}

pkg/exec/cli_linux.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// +build linux
2+
3+
package exec
4+
5+
import (
6+
"github.com/pyroscope-io/pyroscope/pkg/util/caps"
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
func performOSChecks() {
11+
if !hasSysPtraceCap() {
12+
logrus.Fatal("if you're running pyroscope in a Docker container, add --cap-add=sys_ptrace. See our Docker Guide for more information: https://pyroscope.io/docs/docker-guide")
13+
}
14+
}
15+
16+
// See linux source code: https://github.com/torvalds/linux/blob/6ad4bf6ea1609fb539a62f10fca87ddbd53a0315/include/uapi/linux/capability.h#L235
17+
const CAP_SYS_PTRACE = 19
18+
19+
func hasSysPtraceCap() bool {
20+
c, err := caps.Get()
21+
if err != nil {
22+
logrus.Warn("Could not read capabilities. Please submit an issue at https://github.com/pyroscope-io/pyroscope/issues")
23+
return true // I don't know of cases when this would happen, but if it does I'd rather give this program a chance
24+
}
25+
26+
if c.Inheritable() == 0 {
27+
logrus.Warn("Could not read capabilities. Please submit an issue at https://github.com/pyroscope-io/pyroscope/issues")
28+
return true // I don't know of cases when this would happen, but if it does I'd rather give this program a chance
29+
}
30+
31+
return c.Has(CAP_SYS_PTRACE)
32+
}

pkg/util/caps/capabilities.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build linux
6+
7+
// this is copied from https://golang.org/src/syscall/exec_linux_test.go
8+
9+
package caps
10+
11+
import (
12+
"fmt"
13+
"syscall"
14+
"unsafe"
15+
)
16+
17+
type header struct {
18+
version uint32
19+
pid int32
20+
}
21+
22+
type data struct {
23+
effective uint32
24+
permitted uint32
25+
inheritable uint32
26+
}
27+
28+
const CAP_SYS_TIME = 25
29+
const CAP_SYSLOG = 34
30+
31+
type Caps struct {
32+
header header
33+
data [2]data
34+
}
35+
36+
func Get() (Caps, error) {
37+
var c Caps
38+
39+
// Get capability version
40+
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.header)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
41+
return c, fmt.Errorf("SYS_CAPGET: %v", errno)
42+
}
43+
44+
// Get current capabilities
45+
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.header)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
46+
return c, fmt.Errorf("SYS_CAPGET: %v", errno)
47+
}
48+
49+
return c, nil
50+
}
51+
52+
func (c Caps) Has(capSearch uint) bool {
53+
return (c.data[0].inheritable & (1 << capSearch)) != 0
54+
}
55+
56+
func (c Caps) Inheritable() uint32 {
57+
return c.data[0].inheritable
58+
}

0 commit comments

Comments
 (0)