Skip to content

Commit

Permalink
(#1107) Handle detach/retach operaiton in the cli
Browse files Browse the repository at this point in the history
Closes #1107
  • Loading branch information
michalbiesek committed Sep 16, 2022
1 parent 562e3c0 commit cc582a2
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 70 deletions.
24 changes: 24 additions & 0 deletions cli/cmd/detach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cmd

import (
"github.com/criblio/scope/internal"
"github.com/spf13/cobra"
)

// detachCmd represents the run command
var detachCmd = &cobra.Command{
Use: "detach [flags] PID | <process_name>",
Short: "Unscope a currently-running process",
Long: `Unscopes a currently-running process identified by PID or <process_name>.`,
Example: `scope detach 1000
scope detach firefox`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
internal.InitConfig()
return rc.Detach(args)
},
}

func init() {
RootCmd.AddCommand(detachCmd)
}
12 changes: 12 additions & 0 deletions cli/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,15 @@ func (sL *ScopeLoader) AttachSubProc(args []string, env []string) (string, error
args = append([]string{"--attach"}, args...)
return sL.RunSubProc(args, env)
}

// Detach transforms the calling process into a ldscope detach operation
func (sL *ScopeLoader) Detach(args []string, env []string) error {
args = append([]string{"--detach"}, args...)
return sL.Run(args, env)
}

// DetachSubProc runs a ldscope detach as a seperate process
func (sL *ScopeLoader) DetachSubProc(args []string, env []string) (string, error) {
args = append([]string{"--detach"}, args...)
return sL.RunSubProc(args, env)
}
167 changes: 104 additions & 63 deletions cli/run/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,62 +21,43 @@ var (
errPidInvalid = errors.New("invalid PID")
errPidMissing = errors.New("PID does not exist")
errCreateLdscope = errors.New("error creating ldscope")
errAlreadyScope = errors.New("attach failed. This process is already being scoped")
errNotScoped = errors.New("detach failed. This process is not being scoped")
errLibraryNotExist = errors.New("library Path does not exist")
errInvalidSelection = errors.New("invalid Selection")
)

// Attach scopes an existing PID
func (rc *Config) Attach(args []string) error {
// Validate user has root permissions
if err := util.UserVerifyRootPerm(); err != nil {
return err
}
// Validate PTRACE capability
c, err := capability.NewPid2(0)
pid, err := handleInputPid(args[0])
if err != nil {
return errGetLinuxCap
}
err = c.Load()
if err != nil {
return errLoadLinuxCap
}
if !c.Get(capability.EFFECTIVE, capability.CAP_SYS_PTRACE) {
return errMissingPtrace
return err
}
// Get PID by name if non-numeric, otherwise validate/use args[0]
var pid int
if !util.IsNumeric(args[0]) {
procs, err := util.ProcessesByName(args[0])
if err != nil {
args[0] = fmt.Sprint(pid)
var reattach bool
// Check PID is not already being scoped
if !util.PidScoped(pid) {
// Validate user has root permissions
if err := util.UserVerifyRootPerm(); err != nil {
return err
}
if len(procs) == 1 {
pid = procs[0].Pid
} else if len(procs) > 1 {
fmt.Println("Found multiple processes matching that name...")
pid, err = choosePid(procs)
if err != nil {
return err
}
} else {
return errMissingProc
}
args[0] = fmt.Sprint(pid)
} else {
pid, err = strconv.Atoi(args[0])
// Validate PTRACE capability
c, err := capability.NewPid2(0)
if err != nil {
return errPidInvalid
return errGetLinuxCap
}

if err = c.Load(); err != nil {
return errLoadLinuxCap
}

if !c.Get(capability.EFFECTIVE, capability.CAP_SYS_PTRACE) {
return errMissingPtrace
}
} else {
// Reattach because process contains our library
reattach = true
}
// Check PID exists
if !util.PidExists(pid) {
return errPidMissing
}
// Check PID is not already being scoped
if util.PidScoped(pid) {
return errAlreadyScope
}

// Create ldscope
if err := createLdscope(); err != nil {
return errCreateLdscope
Expand All @@ -101,30 +82,90 @@ func (rc *Config) Attach(args []string) error {
// Prepend "-f" [PATH] to args
args = append([]string{"-f", rc.LibraryPath}, args...)
}
sL := loader.ScopeLoader{Path: ldscopePath()}
if reattach {
env = append(env, "SCOPE_CONF_RELOAD="+filepath.Join(rc.WorkDir, "scope.yml"))
}

ld := loader.ScopeLoader{Path: ldscopePath()}
if !rc.Subprocess {
return ld.Attach(args, env)
}
_, err = ld.AttachSubProc(args, env)
return err
}

// Detach unscopes an existing PID
func (rc *Config) Detach(args []string) error {
pid, err := handleInputPid(args[0])
if err != nil {
return err
}
args[0] = fmt.Sprint(pid)

// Check PID is already being scoped
if !util.PidScoped(pid) {
return errNotScoped
}

env := os.Environ()

// Create ldscope
if err := createLdscope(); err != nil {
return errCreateLdscope
}

ld := loader.ScopeLoader{Path: ldscopePath()}
if !rc.Subprocess {
return sL.Attach(args, env)
return ld.Detach(args, env)
}
_, err = sL.AttachSubProc(args, env)
_, err = ld.DetachSubProc(args, env)
return err
}

// choosePid presents a user interface for selecting a PID
func choosePid(procs util.Processes) (int, error) {
util.PrintObj([]util.ObjField{
{Name: "ID", Field: "id"},
{Name: "Pid", Field: "pid"},
{Name: "User", Field: "user"},
{Name: "Scoped", Field: "scoped"},
{Name: "Command", Field: "command"},
}, procs)
fmt.Println("Select an ID from the list:")
var selection string
fmt.Scanln(&selection)
i, err := strconv.ParseUint(selection, 10, 32)
i--
if err != nil || i >= uint64(len(procs)) {
return -1, errInvalidSelection
// handleInputPid handles the input argument
func handleInputPid(InputArg string) (int, error) {
// Get PID by name if non-numeric, otherwise validate/use InputArg
var pid int
var err error
if !util.IsNumeric(InputArg) {
procs, err := util.ProcessesByName(InputArg)
if err != nil {
return -1, err
}
if len(procs) == 1 {
pid = procs[0].Pid
} else if len(procs) > 1 {
// user interface for selecting a PID
fmt.Println("Found multiple processes matching that name...")
util.PrintObj([]util.ObjField{
{Name: "ID", Field: "id"},
{Name: "Pid", Field: "pid"},
{Name: "User", Field: "user"},
{Name: "Scoped", Field: "scoped"},
{Name: "Command", Field: "command"},
}, procs)
fmt.Println("Select an ID from the list:")
var selection string
fmt.Scanln(&selection)
i, err := strconv.ParseUint(selection, 10, 32)
i--
if err != nil || i >= uint64(len(procs)) {
return -1, errInvalidSelection
}
} else {
return -1, errMissingProc
}
} else {
pid, err = strconv.Atoi(InputArg)
if err != nil {
return -1, errPidInvalid
}
}

// Check PID exists
if !util.PidExists(pid) {
return -1, errPidMissing
}
return procs[i].Pid, nil

return pid, nil
}
27 changes: 21 additions & 6 deletions src/scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,10 @@ attach(pid_t pid, char *scopeLibPath)
}

static int
attachCmd(pid_t pid, const char *on_off)
attachCmd(pid_t pid, bool attach)
{
int fd;
char path[PATH_MAX];
char cmd[64];

scope_snprintf(path, sizeof(path), "%s/%s.%d",
DYN_CONFIG_CLI_DIR, DYN_CONFIG_CLI_PREFIX, pid);
Expand Down Expand Up @@ -143,14 +142,30 @@ attachCmd(pid_t pid, const char *on_off)
return EXIT_FAILURE;
}
}

scope_snprintf(cmd, sizeof(cmd), "SCOPE_CMD_ATTACH=%s", on_off);
const char *cmd = (attach == TRUE) ? "SCOPE_CMD_ATTACH=true" : "SCOPE_CMD_ATTACH=false";
if (scope_write(fd, cmd, scope_strlen(cmd)) <= 0) {
scope_perror("scope_write() failed");
scope_close(fd);
return EXIT_FAILURE;
}

if (attach == TRUE) {
/*
* Reload the configuration during reattach if we want to redirect data
* into other place e.g via cli
*/
char *scopeConfReload = getenv("SCOPE_CONF_RELOAD");
if (scopeConfReload) {
char reloadCmd[PATH_MAX] = {0};
scope_snprintf(reloadCmd, sizeof(reloadCmd), "\nSCOPE_CONF_RELOAD=%s", scopeConfReload);
if (scope_write(fd, reloadCmd, scope_strlen(reloadCmd)) <= 0) {
scope_perror("scope_write() failed");
scope_close(fd);
return EXIT_FAILURE;
}
}
}

scope_close(fd);
return EXIT_SUCCESS;
}
Expand Down Expand Up @@ -262,7 +277,7 @@ main(int argc, char **argv, char **env)
} else {
// libscope exists, a reattach
scope_printf("Reattaching to pid %d\n", pid);
ret = attachCmd(pid, "true");
ret = attachCmd(pid, TRUE);
}

// remove the env var file
Expand All @@ -280,7 +295,7 @@ main(int argc, char **argv, char **env)
return EXIT_FAILURE;
}
scope_printf("Detaching from pid %d\n", pid);
return attachCmd(pid, "false");
return attachCmd(pid, FALSE);
} else {
scope_fprintf(scope_stderr, "error: attach or detach with invalid option\n");
showUsage(scope_basename(argv[0]));
Expand Down
20 changes: 19 additions & 1 deletion test/integration/cli/test_cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,15 @@ sleep_pid=$!
run scope attach $sleep_pid
returns 0

# Wait for attach to execute, then end sleep process
# Wait for attach to execute
sleep 2

# Detach to sleep process by PID
run scope detach $sleep_pid
outputs "Detaching from pid ${sleep_pid}"
returns 0

# End sleep process
kill $sleep_pid

# Assert .scope directory exists
Expand Down Expand Up @@ -161,8 +168,19 @@ returns 0

endtest

#
# Scope detach
#
starttest "Scope detach"

#
# Detach by name
#
run scope detach sleep
outputs "Detaching from pid ${sleep_pid}"
returns 0

endtest


################# END TESTS #################
Expand Down

0 comments on commit cc582a2

Please sign in to comment.