Skip to content

Commit

Permalink
linux, exec: adds capabilities detection - prints a message when runn…
Browse files Browse the repository at this point in the history
…ing pyroscope exec with no proper capabilities
  • Loading branch information
petethepig committed Feb 7, 2021
1 parent 30ec4c0 commit e37a9a0
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 21 deletions.
28 changes: 7 additions & 21 deletions pkg/exec/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os/exec"
"os/user"
"path"
"runtime"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -37,13 +36,12 @@ func Cli(cfg *config.Config, args []string) error {
supportedSpies := spy.SupportedExecSpies()
suggestedCommand := fmt.Sprintf("pyroscope exec -spy-name %s %s", supportedSpies[0], strings.Join(args, " "))
return fmt.Errorf(
"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",
"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",
baseName,
color.YellowString("-spy-name"),
color.YellowString(suggestedCommand),
strings.Join(supportedSpies, ","),
armMessage(),
color.BlueString("https://github.com/pyroscope-io/pyroscope/issues"),
color.GreenString("https://github.com/pyroscope-io/pyroscope/issues"),
)
}
}
Expand All @@ -57,7 +55,7 @@ func Cli(cfg *config.Config, args []string) error {
if cfg.Exec.ApplicationName == "" {
logrus.Infof("we recommend specifying application name via %s flag or env variable %s", color.YellowString("-application-name"), color.YellowString("PYROSCOPE_APPLICATION_NAME"))
cfg.Exec.ApplicationName = spyName + "." + names.GetRandomName(generateSeed(args))
logrus.Infof("for now we chose the name for you and it's \"%s\"", color.BlueString(cfg.Exec.ApplicationName))
logrus.Infof("for now we chose the name for you and it's \"%s\"", color.GreenString(cfg.Exec.ApplicationName))
}

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

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

if runtime.GOOS == "darwin" {
if !isRoot() {
logrus.Fatal("on macOS you're required to run the agent with sudo")
}
}
performOSChecks()

if !stringsContains(spy.SupportedSpies, spyName) {
supportedSpies := spy.SupportedExecSpies()
return fmt.Errorf(
"Spy \"%s\" is not supported. Available spies are: %s\n%s",
color.BlueString(spyName),
"Spy \"%s\" is not supported. Available spies are: %s\n",
color.GreenString(spyName),
strings.Join(supportedSpies, ","),
armMessage(),
)
}

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

func armMessage() string {
if runtime.GOARCH == "arm64" {
return "Note that rbspy is not available on arm64 platform"
}
return ""
}

func generateSeed(args []string) string {
path, err := os.Getwd()
if err != nil {
Expand Down
13 changes: 13 additions & 0 deletions pkg/exec/cli_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// +build darwin

package exec

import (
"github.com/sirupsen/logrus"
)

func performOSChecks() {
if !isRoot() {
logrus.Fatal("on macOS you're required to run the agent with sudo")
}
}
32 changes: 32 additions & 0 deletions pkg/exec/cli_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// +build linux

package exec

import (
"github.com/pyroscope-io/pyroscope/pkg/util/caps"
"github.com/sirupsen/logrus"
)

func performOSChecks() {
if !hasSysPtraceCap() {
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")
}
}

// See linux source code: https://github.com/torvalds/linux/blob/6ad4bf6ea1609fb539a62f10fca87ddbd53a0315/include/uapi/linux/capability.h#L235
const CAP_SYS_PTRACE = 19

func hasSysPtraceCap() bool {
c, err := caps.Get()
if err != nil {
logrus.Warn("Could not read capabilities. Please submit an issue at https://github.com/pyroscope-io/pyroscope/issues")
return true // I don't know of cases when this would happen, but if it does I'd rather give this program a chance
}

if c.Inheritable() == 0 {
logrus.Warn("Could not read capabilities. Please submit an issue at https://github.com/pyroscope-io/pyroscope/issues")
return true // I don't know of cases when this would happen, but if it does I'd rather give this program a chance
}

return c.Has(CAP_SYS_PTRACE)
}
58 changes: 58 additions & 0 deletions pkg/util/caps/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux

// this is copied from https://golang.org/src/syscall/exec_linux_test.go

package caps

import (
"fmt"
"syscall"
"unsafe"
)

type header struct {
version uint32
pid int32
}

type data struct {
effective uint32
permitted uint32
inheritable uint32
}

const CAP_SYS_TIME = 25
const CAP_SYSLOG = 34

type Caps struct {
header header
data [2]data
}

func Get() (Caps, error) {
var c Caps

// Get capability version
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.header)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
return c, fmt.Errorf("SYS_CAPGET: %v", errno)
}

// Get current capabilities
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.header)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
return c, fmt.Errorf("SYS_CAPGET: %v", errno)
}

return c, nil
}

func (c Caps) Has(capSearch uint) bool {
return (c.data[0].inheritable & (1 << capSearch)) != 0
}

func (c Caps) Inheritable() uint32 {
return c.data[0].inheritable
}

0 comments on commit e37a9a0

Please sign in to comment.