Skip to content

Commit 578c57f

Browse files
committed
[runner] Check capabilities(7)
1 parent f7dacf7 commit 578c57f

4 files changed

Lines changed: 79 additions & 12 deletions

File tree

runner/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/urfave/cli/v3 v3.6.1
2424
golang.org/x/crypto v0.22.0
2525
golang.org/x/sys v0.26.0
26+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.77
2627
)
2728

2829
require (
@@ -84,4 +85,5 @@ require (
8485
gopkg.in/warnings.v0 v0.1.2 // indirect
8586
gopkg.in/yaml.v3 v3.0.1 // indirect
8687
gotest.tools/v3 v3.5.1 // indirect
88+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect
8789
)

runner/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
321321
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
322322
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
323323
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
324+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.77 h1:iQtQTjFUOcTT19fI8sTCzYXsjeVs56et3D8AbKS2Uks=
325+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.77/go.mod h1:oV+IO8kGh0B7TxErbydDe2+BRmi9g/W0CkpVV+QBTJU=
326+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 h1:Z06sMOzc0GNCwp6efaVrIrz4ywGJ1v+DP0pjVkOfDuA=
327+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

runner/internal/executor/executor.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/dstackai/dstack/runner/consts"
2727
"github.com/dstackai/dstack/runner/internal/common"
2828
"github.com/dstackai/dstack/runner/internal/connections"
29+
cap "github.com/dstackai/dstack/runner/internal/linux/capabilities"
2930
linuxuser "github.com/dstackai/dstack/runner/internal/linux/user"
3031
"github.com/dstackai/dstack/runner/internal/log"
3132
"github.com/dstackai/dstack/runner/internal/schemas"
@@ -467,10 +468,19 @@ func (ex *RunExecutor) execJob(ctx context.Context, jobLogFile io.Writer) error
467468
}
468469
cmd.Dir = ex.jobWorkingDir
469470

470-
// Strictly speaking, we need CAP_SETUID and CAP_GUID (for Cmd.Start()->
471-
// Cmd.SysProcAttr.Credential) and CAP_CHOWN (for startCommand()->os.Chown()),
472-
// but for the sake of simplicity we instead check if we are root or not
473-
if ex.currentUser.IsRoot() {
471+
// CAP_SET{UID,GID} for startCommand() -> Cmd.Start() -> set{uid,gid,groups} syscalls during fork-exec
472+
// CAP_CHOWN for startCommand() -> os.Chown(pts.Name())
473+
if missing, err := cap.Check(cap.SETUID, cap.SETGID, cap.CHOWN); err != nil {
474+
log.Error(
475+
ctx, "Failed to check capabilities, won't try to set process credentials",
476+
"err", err, "user", ex.currentUser,
477+
)
478+
} else if len(missing) > 0 {
479+
log.Info(
480+
ctx, "Required capabilities are missing, cannot set process credentials",
481+
"missing", missing, "user", ex.currentUser,
482+
)
483+
} else {
474484
log.Trace(ctx, "Using credentials", "user", ex.jobUser)
475485
if cmd.SysProcAttr == nil {
476486
cmd.SysProcAttr = &syscall.SysProcAttr{}
@@ -480,10 +490,7 @@ func (ex *RunExecutor) execJob(ctx context.Context, jobLogFile io.Writer) error
480490
return fmt.Errorf("prepare process credentials: %w", err)
481491
}
482492
cmd.SysProcAttr.Credential = creds
483-
} else {
484-
log.Info(ctx, "Current user is not root, cannot set process credentials", "user", ex.currentUser)
485493
}
486-
487494
envMap := NewEnvMap(ParseEnvList(os.Environ()), jobEnvs, ex.secrets)
488495
// `env` interpolation feature is postponed to some future release
489496
envMap.Update(ex.jobSpec.Env, false)
@@ -509,11 +516,15 @@ func (ex *RunExecutor) execJob(ctx context.Context, jobLogFile io.Writer) error
509516
// Note: we already set RLIMIT_MEMLOCK to unlimited in the shim if we've detected IB devices
510517
// (see configureHpcNetworkingIfAvailable() function), but, as it's on the shim side, it only works
511518
// with VM-based backends.
512-
rlimitMemlock := unix.Rlimit{Cur: unix.RLIM_INFINITY, Max: unix.RLIM_INFINITY}
513-
// TODO: Check if we have CAP_SYS_RESOURCE. In container environments, even root usually doesn't have
514-
// this capability.
515-
if err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &rlimitMemlock); err != nil {
516-
log.Error(ctx, "Failed to set resource limits", "err", err)
519+
if ok, err := cap.Has(cap.SYS_RESOURCE); err != nil {
520+
log.Error(ctx, "Failed to check capabilities, won't try to set resource limits", "err", err)
521+
} else if !ok {
522+
log.Info(ctx, "Required capability is missing, cannot set resource limits", "missing", cap.SYS_RESOURCE)
523+
} else {
524+
rlimitMemlock := unix.Rlimit{Cur: unix.RLIM_INFINITY, Max: unix.RLIM_INFINITY}
525+
if err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &rlimitMemlock); err != nil {
526+
log.Error(ctx, "Failed to set resource limits", "err", err)
527+
}
517528
}
518529

519530
// HOME must be added after writeDstackProfile to avoid overriding the correct per-user value set by sshd
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package capabilities
2+
3+
import (
4+
"strings"
5+
6+
"kernel.org/pub/linux/libs/security/libcap/cap"
7+
)
8+
9+
type Capability cap.Value
10+
11+
const (
12+
SETUID = Capability(cap.SETUID)
13+
SETGID = Capability(cap.SETGID)
14+
CHOWN = Capability(cap.CHOWN)
15+
SYS_RESOURCE = Capability(cap.SYS_RESOURCE)
16+
)
17+
18+
// String returns a text representation of the capability in the form used by container folks:
19+
// UPPER_CASE, no CAP_ prefix: cap_sys_admin -> SYS_ADMIN
20+
func (c Capability) String() string {
21+
return strings.ToUpper(cap.Value(c).String()[4:])
22+
}
23+
24+
// Has returns true if the current process has the specified capability in its effective set
25+
func Has(c Capability) (bool, error) {
26+
set, err := cap.GetPID(0)
27+
if err != nil {
28+
return false, err
29+
}
30+
return set.GetFlag(cap.Effective, cap.Value(c))
31+
}
32+
33+
// Check checks and returns those capabilities that are _missing_ from the effective set
34+
// of the current process
35+
func Check(cs ...Capability) (missing []Capability, err error) {
36+
set, err := cap.GetPID(0)
37+
if err != nil {
38+
return nil, err
39+
}
40+
for _, c := range cs {
41+
ok, err := set.GetFlag(cap.Effective, cap.Value(c))
42+
if err != nil {
43+
return nil, err
44+
}
45+
if !ok {
46+
missing = append(missing, c)
47+
}
48+
}
49+
return missing, nil
50+
}

0 commit comments

Comments
 (0)