Skip to content

Commit

Permalink
(#1096) Detect AppScope library state in the application
Browse files Browse the repository at this point in the history
- check the state of application if was unscoped/scoped/loaded
- provide a dummy mapping for the loaded state

Closes #1096
  • Loading branch information
michalbiesek committed Sep 16, 2022
1 parent 0e44b30 commit ab8d128
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 56 deletions.
1 change: 1 addition & 0 deletions cli/cmd/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var psCmd = &cobra.Command{
{Name: "Pid", Field: "pid"},
{Name: "User", Field: "user"},
{Name: "Command", Field: "command"},
{Name: "State", Field: "scoped"},
}, procs)
},
}
Expand Down
6 changes: 3 additions & 3 deletions cli/run/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func (rc *Config) Attach(args []string) error {
return errPidMissing
}
// Check PID is not already being scoped
if util.PidScoped(pid) {
return errAlreadyScope
if util.PidScopeState(pid) == util.Scoped {
util.ErrAndExit("Attach failed. This process is already being scoped")
}
// Create ldscope
if err := createLdscope(); err != nil {
Expand Down Expand Up @@ -115,7 +115,7 @@ func choosePid(procs util.Processes) (int, error) {
{Name: "ID", Field: "id"},
{Name: "Pid", Field: "pid"},
{Name: "User", Field: "user"},
{Name: "Scoped", Field: "scoped"},
{Name: "State", Field: "scoped"},
{Name: "Command", Field: "command"},
}, procs)
fmt.Println("Select an ID from the list:")
Expand Down
2 changes: 1 addition & 1 deletion cli/run/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func startAttach(allowProc allowProcConfig) error {
}

for pid, scopeState := range pidsToAttach {
if scopeState {
if scopeState == util.Scoped {
log.Info().
Str("pid", strconv.Itoa(pid)).
Msgf("Attach Failed. Process: %v is already scoped.", pid)
Expand Down
72 changes: 46 additions & 26 deletions cli/util/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,30 @@ import (
linuxproc "github.com/c9s/goprocinfo/linux"
)

type ScopeState int

const (
Unscoped ScopeState = iota
Scoped
Loaded
Unknown
)

func (ss ScopeState) String() string {
return []string{"Unscoped", "Scoped", "Loaded", "Unknown"}[ss]
}

// Process is a unix process
type Process struct {
ID int `json:"id"`
Pid int `json:"pid"`
User string `json:"user"`
Scoped bool `json:"scoped"`
Command string `json:"command"`
ID int `json:"id"`
Pid int `json:"pid"`
User string `json:"user"`
ScopeStatus ScopeState `json:"scoped"`
Command string `json:"command"`
}

// PidScopeMap is a map of Pid and Scope state
type PidScopeMapState map[int]bool
type PidScopeMapState map[int]ScopeState

// Processes is an array of Process
type Processes []Process
Expand Down Expand Up @@ -85,7 +98,7 @@ func pidScopeMapSearch(inputArg string, sF searchFun) (PidScopeMapState, error)
}

if sF(pid, inputArg) {
pidMap[pid] = PidScoped(pid)
pidMap[pid] = PidScopeState(pid)
}
}

Expand Down Expand Up @@ -149,11 +162,11 @@ func ProcessesByName(name string) (Processes, error) {
// Add process if there is a name match
if strings.Contains(command, name) {
processes = append(processes, Process{
ID: i,
Pid: pid,
User: userName,
Scoped: PidScoped(pid),
Command: cmdLine,
ID: i,
Pid: pid,
User: userName,
ScopeStatus: PidScopeState(pid),
Command: cmdLine,
})
i++
}
Expand Down Expand Up @@ -200,15 +213,15 @@ func ProcessesScoped() (Processes, error) {
continue
}

// Add process if is is scoped
scoped := PidScoped(pid)
if scoped {
// Add process if is is scoped or loaded
scopeState := PidScopeState(pid)
if scopeState == Scoped || scopeState == Loaded {
processes = append(processes, Process{
ID: i,
Pid: pid,
User: userName,
Scoped: scoped,
Command: cmdLine,
ID: i,
Pid: pid,
User: userName,
ScopeStatus: scopeState,
Command: cmdLine,
})
i++
}
Expand All @@ -234,24 +247,31 @@ func PidUser(pid int) (string, error) {
return user.Username, nil
}

// PidScoped checks if a process specified by PID is being scoped
func PidScoped(pid int) bool {
// PidScopeState retrieve Scope state of a process specified by PID
func PidScopeState(pid int) ScopeState {
pidMapFile, err := ioutil.ReadFile(fmt.Sprintf("/proc/%v/maps", pid))
if err != nil {
return false
return Unknown
}

// Look for libscope in map
pidMap := string(pidMapFile)
if strings.Contains(pidMap, "libscope") {
command, err := PidCommand(pid)
if err != nil {
return false
return Unknown
}

if command == "ldscopedyn" {
return Unscoped
}
return command != "ldscopedyn"
if strings.Contains(pidMap, "scope_loaded") {
return Loaded
}
return Scoped
}

return false
return Unscoped
}

// PidCommand gets the command used to start the process specified by PID
Expand Down
16 changes: 8 additions & 8 deletions cli/util/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ func TestProcessesByName(t *testing.T) {
user, _ := user.Current()
exp := Processes{
Process{
ID: 1,
Pid: os.Getpid(),
User: user.Username,
Command: strings.Join(os.Args[:], " "),
Scoped: false,
ID: 1,
Pid: os.Getpid(),
User: user.Username,
Command: strings.Join(os.Args[:], " "),
ScopeStatus: Unscoped,
},
}
assert.Equal(t, exp, result)
Expand All @@ -42,7 +42,7 @@ func TestPidScopeMapByProcessName(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, pidMap, 1)
assert.Contains(t, pidMap, os.Getpid())
assert.Equal(t, pidMap[os.Getpid()], false)
assert.Equal(t, pidMap[os.Getpid()], Unscoped)
}

// TestPidScopeMapByProcessName
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestPidScopeMapByCmdLine(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, pidMap, 1)
assert.Contains(t, pidMap, os.Getpid())
assert.Equal(t, pidMap[os.Getpid()], false)
assert.Equal(t, pidMap[os.Getpid()], Unscoped)
}

// TestPidScopeMapByCmdLineCharShorter
Expand All @@ -86,7 +86,7 @@ func TestPidScopeMapByCmdLineCharShorter(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, pidMap, 1)
assert.Contains(t, pidMap, os.Getpid())
assert.Equal(t, pidMap[os.Getpid()], false)
assert.Equal(t, pidMap[os.Getpid()], Unscoped)
}

// TestPidUser
Expand Down
1 change: 1 addition & 0 deletions os/linux/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ coretest: $(C_FILES) $(YAML_AR) $(JSON_AR) $(TEST_LIB)
$(CC) $(TEST_CFLAGS) -o test/$(OS)/httpstatetest httpstatetest.o httpstate.o plattime.o search.o fn.o utils.o os.o scopestdlib.o dbg.o test.o com.o cfg.o cfgutils.o mtc.o mtcformat.o strset.o ctl.o transport.o linklist.o log.o evtformat.o circbuf.o state.o metriccapture.o report.o httpagg.o $(TEST_AR) $(TEST_LD_FLAGS) -Wl,--wrap=cmdPostEvent -lrt
$(CC) $(TEST_CFLAGS) -o test/$(OS)/httpheadertest httpheadertest.o report.o httpagg.o state.o com.o httpstate.o metriccapture.o plattime.o fn.o utils.o os.o ctl.o log.o transport.o scopestdlib.o dbg.o cfgutils.o cfg.o mtc.o evtformat.o mtcformat.o strset.o circbuf.o linklist.o search.o test.o $(TEST_AR) $(TEST_LD_FLAGS) -Wl,--wrap=cmdSendHttp -Wl,--wrap=cmdPostEvent
$(CC) $(TEST_CFLAGS) -o test/$(OS)/httpaggtest httpaggtest.o httpagg.o fn.o utils.o scopestdlib.o dbg.o plattime.o os.o test.o $(TEST_AR) $(TEST_LD_FLAGS)
$(CC) $(TEST_CFLAGS) -o test/$(OS)/libstatetest libstatetest.o libstate.o scopestdlib.o dbg.o test.o $(TEST_AR) $(TEST_LD_FLAGS)
$(CC) $(TEST_CFLAGS) -o test/$(OS)/reporttest reporttest.o report.o httpagg.o state.o httpstate.o metriccapture.o com.o plattime.o fn.o utils.o os.o ctl.o log.o transport.o scopestdlib.o dbg.o cfgutils.o cfg.o mtc.o evtformat.o mtcformat.o strset.o circbuf.o linklist.o search.o test.o $(TEST_AR) $(TEST_LD_FLAGS) -Wl,--wrap=cmdSendEvent -Wl,--wrap=cmdSendMetric
$(CC) $(TEST_CFLAGS) -o test/$(OS)/mtcformattest mtcformattest.o mtcformat.o strset.o scopestdlib.o dbg.o log.o transport.o com.o ctl.o mtc.o evtformat.o cfg.o cfgutils.o linklist.o fn.o utils.o circbuf.o os.o test.o report.o search.o httpagg.o state.o httpstate.o metriccapture.o plattime.o $(TEST_AR) $(TEST_LD_FLAGS)
$(CC) $(TEST_CFLAGS) -o test/$(OS)/circbuftest circbuftest.o circbuf.o scopestdlib.o dbg.o test.o $(TEST_AR) $(TEST_LD_FLAGS)
Expand Down
2 changes: 1 addition & 1 deletion os/linux/aarch64.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ARCH_OBJ=$(ARCH)
LD_FLAGS=$(MUSL_AR) $(UNWIND_AR) $(PCRE2_AR) $(LS_HPACK_AR) $(YAML_AR) $(JSON_AR) -ldl -lpthread -lrt -lresolv -Lcontrib/build/funchook -lfunchook -Lcontrib/build/funchook/capstone_src-prefix/src/capstone_src-build -lcapstone
INCLUDES=-I./contrib/libyaml/include -I./contrib/cJSON -I./os/$(OS) -I./contrib/pcre2/src -I./contrib/build/pcre2 -I./contrib/funchook/capstone_src/include/ -I./contrib/jni -I./contrib/jni/linux/ -I./contrib/openssl/include -I./contrib/build/openssl/include -I./contrib/build/libunwind/include -I./contrib/libunwind/include/

$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/scopeelf.c src/utils.c src/strset.c src/javabci.c src/javaagent.c
$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/libstate.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/scopeelf.c src/utils.c src/strset.c src/javabci.c src/javaagent.c
@$(MAKE) -C contrib funchook pcre2 openssl ls-hpack musl libyaml libunwind cJSON
@echo "$${CI:+::group::}Building $@"
$(CC) $(CFLAGS) \
Expand Down
2 changes: 1 addition & 1 deletion os/linux/x86_64.mk
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ INCLUDES=-I./contrib/libyaml/include -I./contrib/cJSON -I./os/$(OS) -I./contrib/
# objcopy -I binary -O elf64-x86-64 -B i386 ./lib/$(OS)/libscope.so ./lib/$(OS)/libscope.o && \
# rm -f ./lib/$(OS)/libscope.so

$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/sysexec.c src/gocontext.S src/scopeelf.c src/wrap_go.c src/utils.c src/strset.c src/javabci.c src/javaagent.c
$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/libstate.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/sysexec.c src/gocontext.S src/scopeelf.c src/wrap_go.c src/utils.c src/strset.c src/javabci.c src/javaagent.c
@$(MAKE) -C contrib funchook pcre2 openssl ls-hpack musl libyaml cJSON libunwind
@echo "$${CI:+::group::}Building $@"
$(CC) $(CFLAGS) $(ARCH_CFLAGS) \
Expand Down
82 changes: 82 additions & 0 deletions src/libstate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include "libstate.h"
#include <fcntl.h>
#include "scopestdlib.h"

static void *loadedStrMap = NULL;
#define LOADED_MAP_SIZE (4096)

/*
* AppScope state describes the process state in context of the AppScope library.
* We recognize two states:
* - Scoped - when all the functions are funchooked
* - AppScope library is loaded - when execve family functions are funchooked
*
* The distinguish between the AppScope states is based on presence of the "loaded mapping".
*
* Example loaded mapping view in /proc/<PID>/maps:
* 7fa2b4734000-7fa2b4735000 rw-s 00000000 103:07 23856559 /tmp/scope_loaded.2142471 (deleted)
*/

/*
* libstateLoaded sets the loaded state for the current process by:
* - creating a temporary file `/tmp/scope_loaded.<pid>`.
* - the file is mapped into the process memory as "loaded mapping"
* - the file is removed (unlinked)
*/
bool
libstateLoaded(pid_t pid) {
char path[PATH_MAX] = {0};
bool res = FALSE;
/*
* Switching to "loaded" AppScope state:
* We are done if mapping is present.
*/
if (loadedStrMap) {
return TRUE;
}

if (scope_snprintf(path, sizeof(path), "/tmp/scope_loaded.%d", pid) < 0) {
return res;
}

int outFd = scope_open(path, O_RDWR | O_CREAT, 0664);
if (outFd == -1) {
return res;
}

if (scope_ftruncate(outFd, LOADED_MAP_SIZE) != 0) {
goto close_file;
}

void* dest = scope_mmap(NULL, LOADED_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, outFd, 0);
if (dest == MAP_FAILED) {
goto close_file;
}

loadedStrMap = dest;

res = TRUE;

close_file:
scope_close(outFd);
if (scope_unlink(path) != 0 ) {
return FALSE;
}
return res;
}

/*
* libstateScoped sets the scoped state for the current process by unmap the "loaded mapping"
*/
bool
libstateScoped(void) {
/*
* Switching to "scoped" AppScope state:
* We remove "loaded mapping" if present.
*/
if (loadedStrMap != NULL ) {
scope_munmap(loadedStrMap, LOADED_MAP_SIZE);
loadedStrMap = NULL;
}
return TRUE;
}
10 changes: 10 additions & 0 deletions src/libstate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

#ifndef __LIBSTATE_H__
#define __LIBSTATE_H__

#include "scopetypes.h"

bool libstateLoaded(pid_t);
bool libstateScoped(void);

#endif // __LIBSTATE_H__
2 changes: 1 addition & 1 deletion src/runtimecfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "cfg.h"

typedef struct rtconfig_t {
bool funcs_attached; // TRUE when functions are interposed, FALSE otherwise
bool funcs_scoped; // TRUE when all the functions are interposed, FALSE if only exec* are
int blockconn;
config_t *staticfg;
} rtconfig;
Expand Down
Loading

0 comments on commit ab8d128

Please sign in to comment.