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
8 changes: 8 additions & 0 deletions pkg/abi/linux/prctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ const (
PR_SET_TAGGED_ADDR_CTRL = 55
PR_GET_TAGGED_ADDR_CTRL = 56
PR_TAGGED_ADDR_ENABLE = (1 << 0)

// PR_CAP_AMBIENT controls ambient capabilities.
PR_CAP_AMBIENT = 47

PR_CAP_AMBIENT_IS_SET = 1
PR_CAP_AMBIENT_RAISE = 2
PR_CAP_AMBIENT_LOWER = 3
PR_CAP_AMBIENT_CLEAR_ALL = 4
)

// From <asm/prctl.h>
Expand Down
1 change: 1 addition & 0 deletions pkg/sentry/fsimpl/proc/task_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,7 @@ func (s *statusFD) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps)
fmt.Fprintf(buf, "CapAmb:\t%016x\n", creds.AmbientCaps)
fmt.Fprintf(buf, "Seccomp:\t%d\n", s.task.SeccompMode())
// We unconditionally report a single NUMA node. See
// pkg/sentry/syscalls/linux/sys_mempolicy.go.
Expand Down
24 changes: 18 additions & 6 deletions pkg/sentry/kernel/auth/capability_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ func ComputeCredsForExec(c *Credentials, f FilePrivileges, filename string,
f.SetUserID = NoID
f.SetGroupID = NoID
}
if noNewPrivs {
f.HasCaps = false
f.Effective = false
}
// "...if either the user or the group ID of the file has no mapping inside the namespace, the
// set-user-ID (set-group-ID) bit is silently ignored: the new program is executed, but the
// process's effective user (group) ID is left unchanged." - user_namespaces(7).
Expand All @@ -260,10 +264,16 @@ func ComputeCredsForExec(c *Credentials, f FilePrivileges, filename string,
newC.EffectiveKGID = f.SetGroupID
}

newC.PermittedCaps = CapabilitySet(0)
fileIsPrivileged := f.SetUserID.Ok() || f.SetGroupID.Ok() || f.HasCaps
ambientCaps := c.AmbientCaps
if fileIsPrivileged {
ambientCaps = 0
}

newC.PermittedCaps = ambientCaps
if f.HasCaps {
// P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding))
newC.PermittedCaps = (c.InheritableCaps & f.InheritableCaps) | (f.PermittedCaps & c.BoundingCaps)
// P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient)
newC.PermittedCaps |= (c.InheritableCaps & f.InheritableCaps) | (f.PermittedCaps & c.BoundingCaps)

// The "Safety checking for capability-dumb binaries" section of capabilities(7) says:
// "...For such applications, the effective capability bit is set on the file...
Expand All @@ -290,17 +300,19 @@ func ComputeCredsForExec(c *Credentials, f FilePrivileges, filename string,
newC.SavedKGID = newC.EffectiveKGID

// P'(effective) = effective ? P'(permitted) : P'(ambient).
newC.EffectiveCaps = 0
newC.EffectiveCaps = ambientCaps
if f.Effective {
newC.EffectiveCaps = newC.PermittedCaps
}

newC.AmbientCaps = ambientCaps

// prctl(2): The "keep capabilities" value will be reset to 0 on subsequent calls to execve(2).
newC.KeepCaps = false

root := c.UserNamespace.MapToKUID(RootUID)
// See commoncap.c:cap_bprm_secureexec() in Linux 4.2 (before the introduction of ambient caps).
secureExec := gainedID || (newC.RealKUID != root && (f.Effective || newC.PermittedCaps != CapabilitySet(0)))
// See commoncap.c:cap_bprm_secureexec() in Linux 4.3+.
secureExec := gainedID || (newC.RealKUID != root && (f.Effective || !newC.PermittedCaps.IsSubsetOf(ambientCaps)))
return newC, secureExec, nil
}

Expand Down
68 changes: 63 additions & 5 deletions pkg/sentry/kernel/auth/capability_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import (
)

// credentialsWithCaps creates a credentials object with the given capabilities.
func credentialsWithCaps(inheritable, bounding CapabilitySet) *Credentials {
func credentialsWithCaps(permitted, inheritable, bounding, ambient CapabilitySet) *Credentials {
return NewUserCredentials(1001, 1001, nil, &TaskCapabilities{
PermittedCaps: permitted,
InheritableCaps: inheritable,
BoundingCaps: bounding,
AmbientCaps: ambient,
}, NewRootUserNamespace())
}

Expand Down Expand Up @@ -72,7 +74,7 @@ func TestComputeCredsForExec(t *testing.T) {
PermittedCaps: CapabilitySetOf(linux.CAP_NET_ADMIN),
InheritableCaps: CapabilitySetOf(linux.CAP_NET_ADMIN),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities),
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, 0),
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
wantEffective: true,
},
Expand All @@ -84,7 +86,7 @@ func TestComputeCredsForExec(t *testing.T) {
PermittedCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID}),
InheritableCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETGID}),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities),
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, 0),
wantPermitted: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID, linux.CAP_SETGID}),
wantEffective: true,
},
Expand All @@ -96,7 +98,7 @@ func TestComputeCredsForExec(t *testing.T) {
PermittedCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID}),
InheritableCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETGID}),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities),
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, 0),
wantPermitted: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID, linux.CAP_SETGID}),
wantEffective: false,
},
Expand All @@ -108,9 +110,65 @@ func TestComputeCredsForExec(t *testing.T) {
PermittedCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID}),
InheritableCaps: CapabilitySetOf(linux.CAP_CHOWN),
},
creds: credentialsWithCaps(AllCapabilities, CapabilitySetOf(linux.CAP_CHOWN)),
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_CHOWN), 0),
wantErr: linuxerr.EPERM,
},
{
name: "TestAmbientCapsPreservedNonPrivileged",
filePrivs: FilePrivileges{
HasCaps: false,
Effective: false,
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
wantEffective: true,
},
{
name: "TestAmbientAndFileCapsCombined",
filePrivs: FilePrivileges{
HasCaps: true,
Effective: false,
PermittedCaps: CapabilitySetOf(linux.CAP_CHOWN),
InheritableCaps: CapabilitySetOf(linux.CAP_CHOWN),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
wantPermitted: CapabilitySetOf(linux.CAP_CHOWN),
wantEffective: false,
},
{
name: "TestAmbientCapsPreservedWithFileCapsAndNoNewPrivs",
filePrivs: FilePrivileges{
HasCaps: true,
Effective: false,
PermittedCaps: CapabilitySetOf(linux.CAP_CHOWN),
InheritableCaps: CapabilitySetOf(linux.CAP_CHOWN),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
noNewPrivs: true,
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
wantEffective: true,
},
{
name: "TestAmbientCapsClearedWithSUIDNonRoot",
filePrivs: FilePrivileges{
SetUserID: KUID(1002),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
allowSUID: true,
wantPermitted: 0,
wantEffective: false,
},
{
name: "TestAmbientCapsPreservedWithSUIDAndNoNewPrivs",
filePrivs: FilePrivileges{
SetUserID: KUID(1002),
},
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
noNewPrivs: true,
allowSUID: true,
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
wantEffective: true,
},
} {
t.Run(tst.name, func(t *testing.T) {
newC, _, err := ComputeCredsForExec(tst.creds, tst.filePrivs, "", tst.noNewPrivs, tst.stopPrivGain, tst.allowSUID)
Expand Down
5 changes: 3 additions & 2 deletions pkg/sentry/kernel/auth/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type Credentials struct {
InheritableCaps CapabilitySet
EffectiveCaps CapabilitySet
BoundingCaps CapabilitySet
// Ambient capabilities are not introduced until Linux 4.3.
AmbientCaps CapabilitySet

// KeepCaps is the flag for PR_SET_KEEPCAPS which allow capabilities to be
// maintained after a switch from root user to non-root user via setuid().
Expand Down Expand Up @@ -127,7 +127,7 @@ func NewUserCredentials(kuid KUID, kgid KGID, extraKGIDs []KGID, capabilities *T
creds.EffectiveCaps = capabilities.EffectiveCaps
creds.BoundingCaps = capabilities.BoundingCaps
creds.InheritableCaps = capabilities.InheritableCaps
// TODO(gvisor.dev/issue/3166): Support ambient capabilities.
creds.AmbientCaps = capabilities.AmbientCaps
} else {
// If no capabilities are specified, grant capabilities consistent with
// setresuid + setresgid from NewRootCredentials to the given uid and
Expand Down Expand Up @@ -166,6 +166,7 @@ func (c *Credentials) ForkIntoUserNamespace(ns *UserNamespace) *Credentials {
nc.InheritableCaps = 0
nc.EffectiveCaps = AllCapabilities
nc.BoundingCaps = AllCapabilities
nc.AmbientCaps = 0
// "A call to clone(2), unshare(2), or setns(2) using the CLONE_NEWUSER
// flag sets the "securebits" flags (see capabilities(7)) to their default
// values (all flags disabled) in the child (for clone(2)) or caller (for
Expand Down
47 changes: 47 additions & 0 deletions pkg/sentry/kernel/task_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func (t *Task) setKUIDsUnchecked(newR, newE, newS auth.KUID) {
// nonzero value, then all capabilities are cleared from the permitted and
// effective capability sets." - capabilities(7)
if (oldR == root || oldE == root || oldS == root) && (newR != root && newE != root && newS != root) {
creds.AmbientCaps = 0
// prctl(2): "PR_SET_KEEPCAP: Set the state of the calling thread's
// "keep capabilities" flag, which determines whether the thread's permitted
// capability set is cleared when a change is made to the
Expand Down Expand Up @@ -405,6 +406,7 @@ func (t *Task) SetCapabilitySets(permitted, inheritable, effective auth.Capabili
creds.PermittedCaps = permitted
creds.InheritableCaps = inheritable
creds.EffectiveCaps = effective
creds.AmbientCaps &= permitted & inheritable
t.creds.Store(creds)
return nil
}
Expand Down Expand Up @@ -448,3 +450,48 @@ func (t *Task) GetNoNewPrivs() bool {
defer t.mu.Unlock()
return t.noNewPrivs
}

// AmbientCapability returns whether the capability cp is in the ambient set.
func (t *Task) AmbientCapability(cp linux.Capability) (bool, error) {
if !cp.Ok() {
return false, linuxerr.EINVAL
}
return auth.CapabilitySetOf(cp)&t.Credentials().AmbientCaps != 0, nil
}

// RaiseAmbientCapability adds capability cp to the ambient set.
func (t *Task) RaiseAmbientCapability(cp linux.Capability) error {
if !cp.Ok() {
return linuxerr.EINVAL
}
creds := t.Credentials()
cs := auth.CapabilitySetOf(cp)
// "A capability can be added to the ambient set (PR_CAP_AMBIENT_RAISE) only
// if it is already present in both the permitted and inheritable capability
// sets of the thread." - capabilities(7)
if cs&creds.PermittedCaps == 0 || cs&creds.InheritableCaps == 0 {
return linuxerr.EPERM
}
creds = creds.Fork()
creds.AmbientCaps |= cs
t.creds.Store(creds)
return nil
}

// LowerAmbientCapability removes capability cp from the ambient set.
func (t *Task) LowerAmbientCapability(cp linux.Capability) error {
if !cp.Ok() {
return linuxerr.EINVAL
}
creds := t.Credentials().Fork()
creds.AmbientCaps &^= auth.CapabilitySetOf(cp)
t.creds.Store(creds)
return nil
}

// ClearAmbientCapabilities removes all capabilities from the ambient set.
func (t *Task) ClearAmbientCapabilities() {
creds := t.Credentials().Fork()
creds.AmbientCaps = 0
t.creds.Store(creds)
}
57 changes: 57 additions & 0 deletions pkg/sentry/syscalls/linux/sys_prctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,63 @@ func Prctl(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
}
return 0, nil, t.DropBoundingCapability(cp)

case linux.PR_CAP_AMBIENT:
suboption := args[1].Int()
switch suboption {
case linux.PR_CAP_AMBIENT_IS_SET:
if args[3].Int() != 0 || args[4].Int() != 0 {
return 0, nil, linuxerr.EINVAL
}
cp := linux.Capability(args[2].Uint64())
if !cp.Ok() {
return 0, nil, linuxerr.EINVAL
}
exist, err := t.AmbientCapability(cp)
if err != nil {
return 0, nil, err
}
if exist {
return 1, nil, nil
}
return 0, nil, nil

case linux.PR_CAP_AMBIENT_RAISE:
if args[3].Int() != 0 || args[4].Int() != 0 {
return 0, nil, linuxerr.EINVAL
}
cp := linux.Capability(args[2].Uint64())
if !cp.Ok() {
return 0, nil, linuxerr.EINVAL
}
if err := t.RaiseAmbientCapability(cp); err != nil {
return 0, nil, err
}
return 0, nil, nil

case linux.PR_CAP_AMBIENT_LOWER:
if args[3].Int() != 0 || args[4].Int() != 0 {
return 0, nil, linuxerr.EINVAL
}
cp := linux.Capability(args[2].Uint64())
if !cp.Ok() {
return 0, nil, linuxerr.EINVAL
}
if err := t.LowerAmbientCapability(cp); err != nil {
return 0, nil, err
}
return 0, nil, nil

case linux.PR_CAP_AMBIENT_CLEAR_ALL:
if args[2].Int() != 0 || args[3].Int() != 0 || args[4].Int() != 0 {
return 0, nil, linuxerr.EINVAL
}
t.ClearAmbientCapabilities()
return 0, nil, nil

default:
return 0, nil, linuxerr.EINVAL
}

case linux.PR_SET_CHILD_SUBREAPER:
// "If arg2 is nonzero, set the "child subreaper" attribute of
// the calling process; if arg2 is zero, unset the attribute."
Expand Down
4 changes: 4 additions & 0 deletions test/syscalls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ syscall_test(
test = "//test/syscalls/linux:brk_test",
)

syscall_test(
test = "//test/syscalls/linux:capabilities_test",
)

syscall_test(
one_sandbox = False,
test = "//test/syscalls/linux:cgroup_test",
Expand Down
17 changes: 17 additions & 0 deletions test/syscalls/linux/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,23 @@ cc_library(
alwayslink = 1,
)

cc_binary(
name = "capabilities_test",
testonly = 1,
srcs = ["capabilities.cc"],
linkstatic = 1,
malloc = "//test/util:errno_safe_allocator",
deps = select_gtest() + [
"//test/util:capability_util",
"//test/util:logging",
"//test/util:posix_error",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
"@com_google_absl//absl/time",
],
)

cc_binary(
name = "chdir_test",
testonly = 1,
Expand Down
Loading
Loading