diff --git a/pkg/process/process_manager.go b/pkg/process/process_manager.go index dbdb4cf7f..a241b61cc 100644 --- a/pkg/process/process_manager.go +++ b/pkg/process/process_manager.go @@ -2,6 +2,7 @@ package process import ( "fmt" + "path/filepath" "strconv" "strings" "sync" @@ -111,6 +112,44 @@ func (pm *Manager) Shutdown() { close(pm.shutdownCh) } +func decodeProcessPath(path string) (dir, image, binary string) { + path, binary = filepath.Split(filepath.Clean(path)) + dir, image = filepath.Split(filepath.Clean(path)) + return dir, image, binary +} + +func isValidBinary(binary string) bool { + switch binary { + case "longhorn": + return true + default: + return false + } +} + +func isValidDirectory(dir string) bool { + switch dir { + case "/engine-binaries/", "/host/var/lib/longhorn/engine-binaries/": + return true + default: + return false + } +} + +func ensureValidProcessPath(path string) (string, error) { + dir, image, binary := decodeProcessPath(path) + logrus.Debugf("Process Manager: validate process path: %v dir: %v image: %v binary: %v", path, dir, image, binary) + if !isValidBinary(binary) { + return "", fmt.Errorf("unsupported binary %v", binary) + } + + if !isValidDirectory(dir) { + return "", fmt.Errorf("unsupported process path %v", path) + } + + return filepath.Join(dir, image, binary), nil +} + // ProcessCreate will create a process according to the request. // If the specified process name exists already, the creation will fail. func (pm *Manager) ProcessCreate(ctx context.Context, req *rpc.ProcessCreateRequest) (ret *rpc.ProcessResponse, err error) { @@ -124,9 +163,14 @@ func (pm *Manager) ProcessCreate(ctx context.Context, req *rpc.ProcessCreateRequ return nil, err } + processPath, err := ensureValidProcessPath(req.Spec.Binary) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + p := &Process{ Name: req.Spec.Name, - Binary: req.Spec.Binary, + Binary: processPath, Args: req.Spec.Args, PortCount: req.Spec.PortCount, PortArgs: req.Spec.PortArgs, @@ -381,6 +425,11 @@ func (pm *Manager) ProcessReplace(ctx context.Context, req *rpc.ProcessReplaceRe } terminateSignal := syscall.SIGHUP + processPath, err := ensureValidProcessPath(req.Spec.Binary) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + logrus.Infof("Process Manager: prepare to replace process %v", req.Spec.Name) logger, err := util.NewLonghornWriter(req.Spec.Name, pm.logsDir) if err != nil { @@ -389,7 +438,7 @@ func (pm *Manager) ProcessReplace(ctx context.Context, req *rpc.ProcessReplaceRe p := &Process{ Name: req.Spec.Name, - Binary: req.Spec.Binary, + Binary: processPath, Args: req.Spec.Args, PortCount: req.Spec.PortCount, PortArgs: req.Spec.PortArgs, diff --git a/pkg/process/process_test.go b/pkg/process/process_test.go index 488451983..c028538a2 100644 --- a/pkg/process/process_test.go +++ b/pkg/process/process_test.go @@ -23,9 +23,9 @@ import ( const ( RetryCount = 50 RetryInterval = 100 * time.Millisecond - TestBinary = "any" - TestBinaryMissing = "fail-missing-binary" - TestBinaryReplace = "replacement" + TestBinary = "/engine-binaries/test/longhorn" + TestBinaryMissing = "/engine-binaries/test-missing/longhorn" + TestBinaryReplace = "/engine-binaries/test-replacement/longhorn" ) func Test(t *testing.T) { TestingT(t) } @@ -285,6 +285,62 @@ func (s *TestSuite) TestProcessReplaceDuringDeletion(c *C) { wg.Wait() } +func (s *TestSuite) TestProcessInvalidProcessBinary(c *C) { + + invalidBinaries := []string{ + "/usr/bin/echo", + "/engine-binaries/invalid-binary", + "/host/var/lib/longhorn/engine-binaries/invalid-binary", + "/host/var/lib/longhorn/../longhorn/engine-binaries//../engine-binaries/valid-image/invalid-binary", + } + invalidPaths := []string{ + "longhorn", + "/longhorn", + "/engine-binaries/invalid/image/longhorn", + "/engine-binaries/longhorn", // missing required image folder + "/invalid/path/longhorn", + } + invalidProcess := append(invalidBinaries, invalidPaths...) + + // invalid process creation + for i, binary := range invalidProcess { + name := "test_invalid_process-" + strconv.Itoa(i) + createReq := &rpc.ProcessCreateRequest{ + Spec: createProcessSpec(name, binary), + } + createResp, err := s.pm.ProcessCreate(nil, createReq) + c.Assert(createResp, IsNil) + c.Assert(err, NotNil) + c.Assert(status.Code(err), Equals, codes.InvalidArgument) + } + + // valid process creation + name := "test_valid_process" + assertProcessCreation(c, s.pm, name, TestBinary) + defer func() { + // verify that the original process is not impacted by an invalid process replace call + assertProcessDeletion(c, s.pm, name) + deleted, err := waitForProcessListState(s.pm, func(processes map[string]*rpc.ProcessResponse) bool { + _, exists := processes[name] + return !exists + }) + c.Assert(err, IsNil) + c.Assert(deleted, Equals, true) + }() + + // invalid process replacement + for _, binary := range invalidProcess { + replaceReq := &rpc.ProcessReplaceRequest{ + Spec: createProcessSpec(name, binary), + TerminateSignal: "SIGHUP", + } + rsp, err := s.pm.ProcessReplace(nil, replaceReq) + c.Assert(rsp, IsNil) + c.Assert(err, NotNil) + c.Assert(status.Code(err), Equals, codes.InvalidArgument) + } +} + func assertProcessReplace(c *C, pm *Manager, name, binary string) { replaceReq := &rpc.ProcessReplaceRequest{ Spec: createProcessSpec(name, binary),