Skip to content

Commit

Permalink
(criblio#1108) IPC mechanism - client part
Browse files Browse the repository at this point in the history
  • Loading branch information
michalbiesek committed Dec 16, 2022
1 parent 3a1059c commit 5b8d73b
Show file tree
Hide file tree
Showing 14 changed files with 902 additions and 56 deletions.
34 changes: 26 additions & 8 deletions cli/run/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var (
errGetLinuxCap = errors.New("unable to get linux capabilities for current process")
errLoadLinuxCap = errors.New("unable to load linux capabilities for current process")
errMissingPtrace = errors.New("missing PTRACE capabilities to attach to a process")
errMissingScopedProc = errors.New("no scoped process found matching that name")
errMissingProc = errors.New("no process found matching that name")
errPidInvalid = errors.New("invalid PID")
errPidMissing = errors.New("PID does not exist")
Expand All @@ -30,14 +31,19 @@ var (

// Attach scopes an existing PID
func (rc *Config) Attach(args []string) error {
pid, err := handleInputArg(args[0])
pid, err := handleInputArg(args[0], true)
if err != nil {
return err
}
args[0] = fmt.Sprint(pid)
var reattach bool
// Check PID is not already being scoped
if !util.PidScoped(pid) {
status, err := util.PidScopeStatus(pid)
if err != nil {
return err
}

if status == util.Disable || status == util.Setup {
// Validate user has root permissions
if err := util.UserVerifyRootPerm(); err != nil {
return err
Expand Down Expand Up @@ -113,7 +119,7 @@ func (rc *Config) DetachAll(args []string, prompt bool) error {
fmt.Println("INFO: Run as root (or via sudo) to see all matching processes")
}

procs, err := util.ProcessesScoped()
procs, err := util.ProcessesToDetach()
if err != nil {
return err
}
Expand Down Expand Up @@ -158,7 +164,7 @@ func (rc *Config) DetachAll(args []string, prompt bool) error {

// DetachSingle unscopes an existing PID
func (rc *Config) DetachSingle(args []string) error {
pid, err := handleInputArg(args[0])
pid, err := handleInputArg(args[0], false)
if err != nil {
return err
}
Expand All @@ -173,7 +179,10 @@ func (rc *Config) DetachSingle(args []string) error {

func (rc *Config) detach(args []string, pid int) error {
// Check PID is already being scoped
if !util.PidScoped(pid) {
status, err := util.PidScopeStatus(pid)
if err != nil {
return err
} else if status != util.Active {
return errNotScoped
}

Expand All @@ -189,10 +198,11 @@ func (rc *Config) detach(args []string, pid int) error {
}

// handleInputArg handles the input argument (process id/name)
func handleInputArg(InputArg string) (int, error) {
func handleInputArg(InputArg string, toAttach bool) (int, error) {
// Get PID by name if non-numeric, otherwise validate/use InputArg
var pid int
var err error
var procs util.Processes
if util.IsNumeric(InputArg) {
pid, err = strconv.Atoi(InputArg)
if err != nil {
Expand All @@ -208,7 +218,12 @@ func handleInputArg(InputArg string) (int, error) {
}
}

procs, err := util.ProcessesByName(InputArg)
if toAttach {
procs, err = util.ProcessesByNameToAttach(InputArg)
} else {
procs, err = util.ProcessesByNameToDetach(InputArg)
}

if err != nil {
return -1, err
}
Expand Down Expand Up @@ -241,7 +256,10 @@ func handleInputArg(InputArg string) (int, error) {
if !adminStatus {
fmt.Println("INFO: Run as root (or via sudo) to see all matching processes")
}
return -1, errMissingProc
if toAttach {
return -1, errMissingProc
}
return -1, errMissingScopedProc
}
}

Expand Down
168 changes: 168 additions & 0 deletions cli/util/ipc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package util

import (
"errors"
"fmt"
"os"
"syscall"
"time"
)

// ipc structure representes Inter Process Communication object
type ipcObj struct {
sender *sendMessageQueue // Message queue used to send messages
receiver *receiveMessageQueue // Message queue used to receive messages
ipcSwitch bool // Indicator if IPC switch occured
}

var (
errMissingProcMsgQueue = errors.New("missing message queue from PID")
errMissingResponse = errors.New("missing response from PID")
)

// ipcGetScopeStatus dispatches cmd to the process specified by the pid.
// Returns the byte answer from scoped process endpoint.
func ipcGetScopeStatus(pid int) ([]byte, error) {
return ipcDispatcher(cmdGetScopeStatus, pid)
}

// ipcDispatcher dispatches cmd to the process specified by the pid.
// Returns the byte answer from scoped process endpoint.
func ipcDispatcher(cmd ipcCmd, pid int) ([]byte, error) {
var answer []byte
var responseReceived bool

ipc, err := newIPC(pid)
if err != nil {
return answer, err
}
defer ipc.destroyIPC()

if err := ipc.send(cmd.byte()); err != nil {
return answer, err
}

// TODO: Ugly hack but we need to wait for answer from process
for i := 0; i < 5000; i++ {
if !ipc.empty() {
responseReceived = true
break
}
time.Sleep(time.Millisecond)
}

// Missing response
// The message queue on the application side exists but we are unable to receive
// an answer from it
if !responseReceived {
return answer, fmt.Errorf("%v %v", errMissingResponse, pid)
}

return ipc.receive()
}

// nsnewNonBlockMsgQReader creates an IPC structure with switching effective uid and gid
func nsnewNonBlockMsgQReader(name string, nsUid int, nsGid int, restoreUid int, restoreGid int) (*receiveMessageQueue, error) {

if err := syscall.Setegid(nsGid); err != nil {
return nil, err
}
if err := syscall.Seteuid(nsUid); err != nil {
return nil, err
}

receiver, err := newNonBlockMsgQReader(name)
if err != nil {
return nil, err
}

if err := syscall.Seteuid(restoreUid); err != nil {
return nil, err
}

if err := syscall.Setegid(restoreGid); err != nil {
return nil, err
}

return receiver, err
}

// newIPC creates an IPC object designated for communication with specific PID
func newIPC(pid int) (*ipcObj, error) {
restoreGid := os.Getegid()
restoreUid := os.Geteuid()

ipcSame, err := namespaceSameIpc(pid)
if err != nil {
return nil, err
}

// Retrieve information about user nad group id
nsUid, err := pidNsTranslateUid(restoreUid, pid)
if err != nil {
return nil, err
}
nsGid, err := pidNsTranslateGid(restoreGid, pid)
if err != nil {
return nil, err
}

// Retrieve information about process namespace PID
_, ipcPid, err := pidLastNsPid(pid)
if err != nil {
return nil, err
}

// Switch IPC if neeeded
if !ipcSame {
if err := namespaceSwitchIPC(pid); err != nil {
return nil, err
}
}

// Try to open proc message queue
sender, err := openMsgQWriter(fmt.Sprintf("ScopeIPCIn.%d", ipcPid))
if err != nil {
namespaceRestoreIPC()
return nil, errMissingProcMsgQueue
}

// Try to create own message queue
receiver, err := nsnewNonBlockMsgQReader(fmt.Sprintf("ScopeIPCOut.%d", ipcPid), nsUid, nsGid, restoreUid, restoreGid)
if err != nil {
sender.close()
namespaceRestoreIPC()
return nil, err
}

return &ipcObj{sender: sender, receiver: receiver, ipcSwitch: ipcSame}, nil
}

// destroyIPC destroys an IPC object
func (ipc *ipcObj) destroyIPC() {
ipc.sender.close()
ipc.receiver.close()
ipc.receiver.unlink()
if ipc.ipcSwitch {
namespaceRestoreIPC()
}
}

// receive receive the message from the process endpoint
func (ipc *ipcObj) receive() ([]byte, error) {
return ipc.receiver.receive(0)
}

// empty checks if receiver message queque is empty
func (ipc *ipcObj) empty() bool {
atr, err := ipc.receiver.getAttributes()
if err != nil {
return true
}
return atr.CurrentMessages == 0
}

// send sends the message to the process endpoint
func (ipc *ipcObj) send(msg []byte) error {
return ipc.sender.send(msg, 0)
}
20 changes: 20 additions & 0 deletions cli/util/ipccmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package util

// ipcCmd represents the command structure
type ipcCmd int64

const (
cmdGetScopeStatus ipcCmd = iota
)

func (cmd ipcCmd) string() string {
switch cmd {
case cmdGetScopeStatus:
return "getScopeStatus"
}
return "unknown"
}

func (cmd ipcCmd) byte() []byte {
return []byte(cmd.string())
}
Loading

0 comments on commit 5b8d73b

Please sign in to comment.