drivers/exec: Fix handling of capabilities for unprivileged tasks (#16643)

Currently, the `exec` driver is only setting the Bounding set, which is
not sufficient to actually enable the requisite capabilities for the
task process.  In order for the capabilities to survive `execve`
performed by libcontainer, the `Permitted`, `Inheritable`, and `Ambient`
sets must also be set.

Per CAPABILITIES (7):

> Ambient: This is a set of capabilities that are preserved across an
> execve(2) of a program that is not privileged.  The ambient capability
> set obeys the invariant that no capability can ever be ambient if it
> is not both permitted and inheritable.
This commit is contained in:
Elvis Pranskevichus 2023-03-28 09:12:55 -07:00 committed by GitHub
parent 17fd1a2e35
commit 11a9bb6ce7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 11 deletions

3
.changelog/16643.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
driver/exec: Fixed a bug where `cap_drop` and `cap_add` would not expand capabilities
```

View File

@ -526,8 +526,17 @@ func configureCapabilities(cfg *lconfigs.Config, command *ExecCommand) {
}
default:
// otherwise apply the plugin + task capability configuration
//
// The capabilities must be set in the Ambient set as libcontainer
// performs `execve`` as an unprivileged user. Ambient also requires
// that capabilities are Permitted and Inheritable. Setting Effective
// is unnecessary, because we only need the capabilities to become
// effective _after_ execve, not before.
cfg.Capabilities = &lconfigs.Capabilities{
Bounding: command.Capabilities,
Bounding: command.Capabilities,
Permitted: command.Capabilities,
Inheritable: command.Capabilities,
Ambient: command.Capabilities,
}
}
}

View File

@ -628,27 +628,40 @@ func TestExecutor_Capabilities(t *testing.T) {
testutil.ExecCompatible(t)
cases := []struct {
user string
caps string
user string
capAdd []string
capDrop []string
capsExpected string
}{
{
user: "nobody",
caps: `
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
capsExpected: `
CapInh: 00000000a80405fb
CapPrm: 00000000a80405fb
CapEff: 00000000a80405fb
CapBnd: 00000000a80405fb
CapAmb: 0000000000000000`,
CapAmb: 00000000a80405fb`,
},
{
user: "root",
caps: `
capsExpected: `
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000`,
},
{
user: "nobody",
capDrop: []string{"all"},
capAdd: []string{"net_bind_service"},
capsExpected: `
CapInh: 0000000000000400
CapPrm: 0000000000000400
CapEff: 0000000000000400
CapBnd: 0000000000000400
CapAmb: 0000000000000400`,
},
}
for _, c := range cases {
@ -662,7 +675,17 @@ CapAmb: 0000000000000000`,
execCmd.ResourceLimits = true
execCmd.Cmd = "/bin/bash"
execCmd.Args = []string{"-c", "cat /proc/$$/status"}
execCmd.Capabilities = capabilities.NomadDefaults().Slice(true)
capsBasis := capabilities.NomadDefaults()
capsAllowed := capsBasis.Slice(true)
if c.capDrop != nil || c.capAdd != nil {
calcCaps, err := capabilities.Calculate(
capsBasis, capsAllowed, c.capAdd, c.capDrop)
require.NoError(t, err)
execCmd.Capabilities = calcCaps
} else {
execCmd.Capabilities = capsAllowed
}
executor := NewExecutorWithIsolation(testlog.HCLogger(t))
defer executor.Shutdown("SIGKILL", 0)
@ -690,7 +713,7 @@ CapAmb: 0000000000000000`,
return s
}
expected := canonical(c.caps)
expected := canonical(c.capsExpected)
tu.WaitForResult(func() (bool, error) {
output := canonical(testExecCmd.stdout.String())
if !strings.Contains(output, expected) {