Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/execd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ package main
import (
"fmt"

"github.com/alibaba/opensandbox/internal/version"

_ "go.uber.org/automaxprocs/maxprocs"

"github.com/alibaba/opensandbox/execd/pkg/flag"
"github.com/alibaba/opensandbox/execd/pkg/log"
_ "github.com/alibaba/opensandbox/execd/pkg/util/safego"
"github.com/alibaba/opensandbox/execd/pkg/web"
"github.com/alibaba/opensandbox/execd/pkg/web/controller"
"github.com/alibaba/opensandbox/internal/version"
)

// main initializes and starts the execd server.
Expand Down
29 changes: 12 additions & 17 deletions components/execd/pkg/runtime/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (

func buildCredential(uid, gid *uint32) (*syscall.Credential, error) {
if uid == nil && gid == nil {
return nil, nil
return nil, nil //nolint:nilnil
}

cred := &syscall.Credential{}
Expand Down Expand Up @@ -95,9 +95,20 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest
log.Info("received command: %v", request.Code)
cmd := exec.CommandContext(ctx, "bash", "-c", request.Code)

// Configure credentials and process group
cred, err := buildCredential(request.Uid, request.Gid)
if err != nil {
return fmt.Errorf("failed to build credential: %w", err)
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Credential: cred,
}

cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Env = mergeEnvs(os.Environ(), loadExtraEnvFromFile())
cmd.Dir = request.Cwd

done := make(chan struct{}, 1)
var wg sync.WaitGroup
Expand All @@ -111,20 +122,7 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest
c.tailStdPipe(stderrPath, request.Hooks.OnExecuteStderr, done)
})

cmd.Dir = request.Cwd

// Configure credentials and process group
cred, err := buildCredential(request.Uid, request.Gid)
if err != nil {
log.Error("failed to build credentials: %v", err)
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Credential: cred,
}

err = cmd.Start()

if err != nil {
request.Hooks.OnExecuteInit(session)
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "CommandExecError", EValue: err.Error()})
Expand Down Expand Up @@ -219,9 +217,7 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca
startAt := time.Now()
log.Info("received command: %v", request.Code)
cmd := exec.CommandContext(ctx, "bash", "-c", request.Code)

cmd.Dir = request.Cwd

// Configure credentials and process group
cred, err := buildCredential(request.Uid, request.Gid)
if err != nil {
Expand All @@ -233,7 +229,6 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca
}

cmd.Stdout = pipe

cmd.Stderr = pipe
cmd.Env = mergeEnvs(os.Environ(), loadExtraEnvFromFile())

Expand Down
19 changes: 10 additions & 9 deletions components/execd/pkg/runtime/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@ type ExecuteResultHook struct {

// ExecuteCodeRequest represents a code execution request with context and hooks.
type ExecuteCodeRequest struct {
Language Language `json:"language"`
Code string `json:"code"`
Context string `json:"context"`
Timeout time.Duration `json:"timeout"`
Cwd string `json:"cwd"`
Envs map[string]string `json:"envs"`
Uid *uint32 `json:"uid,omitempty"`
Gid *uint32 `json:"gid,omitempty"`
Hooks ExecuteResultHook
Language Language `json:"language"`
Code string `json:"code"`
Context string `json:"context"`
Timeout time.Duration `json:"timeout"`
Cwd string `json:"cwd"`
Envs map[string]string `json:"envs"`
Uid *uint32 `json:"uid,omitempty"`
Gid *uint32 `json:"gid,omitempty"`
Hooks ExecuteResultHook
}

// SetDefaultHooks installs stdout logging fallbacks for unset hooks.
func (req *ExecuteCodeRequest) SetDefaultHooks() {
if req.Hooks.OnExecuteResult == nil {
Expand Down
4 changes: 4 additions & 0 deletions components/execd/pkg/web/controller/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,17 @@ func (c *CodeInterpretingController) buildExecuteCommandRequest(request model.Ru
Code: request.Command,
Cwd: request.Cwd,
Timeout: timeout,
Gid: request.Gid,
Uid: request.Uid,
}
} else {
return &runtime.ExecuteCodeRequest{
Language: runtime.Command,
Code: request.Command,
Cwd: request.Cwd,
Timeout: timeout,
Gid: request.Gid,
Uid: request.Uid,
}
}
}
3 changes: 3 additions & 0 deletions components/execd/pkg/web/model/codeinterpreting.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type RunCommandRequest struct {
Background bool `json:"background,omitempty"`
// TimeoutMs caps execution duration; 0 uses server default.
TimeoutMs int64 `json:"timeout,omitempty" validate:"omitempty,gte=1"`

Uid *uint32 `json:"uid,omitempty"`
Gid *uint32 `json:"gid,omitempty"`
Comment on lines +57 to +58

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require UID when accepting GID in RunCommandRequest

Allowing gid to be set independently introduces a credential bug: this request is now forwarded to runtime, where buildCredential creates a non-nil syscall.Credential with Gid set but leaves Uid at its zero value; on Linux, a non-nil credential causes setuid(cred.Uid) to run, so gid-only requests attempt setuid(0) and can either fail with EPERM or run as root unexpectedly. This makes gid-only command executions unsafe/unreliable unless validation enforces uid whenever gid is provided (or runtime fills Uid with the current UID).

Useful? React with 👍 / 👎.

}

func (r *RunCommandRequest) Validate() error {
Expand Down
Loading