Skip to content

Commit e6e678c

Browse files
milantracygvisor-bot
authored andcommitted
Implement ambient capabilities.
Fix #3166 PiperOrigin-RevId: 924545721
1 parent da03d3a commit e6e678c

11 files changed

Lines changed: 588 additions & 13 deletions

File tree

pkg/abi/linux/prctl.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ const (
163163
PR_SET_TAGGED_ADDR_CTRL = 55
164164
PR_GET_TAGGED_ADDR_CTRL = 56
165165
PR_TAGGED_ADDR_ENABLE = (1 << 0)
166+
167+
// PR_CAP_AMBIENT controls ambient capabilities.
168+
PR_CAP_AMBIENT = 47
169+
170+
PR_CAP_AMBIENT_IS_SET = 1
171+
PR_CAP_AMBIENT_RAISE = 2
172+
PR_CAP_AMBIENT_LOWER = 3
173+
PR_CAP_AMBIENT_CLEAR_ALL = 4
166174
)
167175

168176
// From <asm/prctl.h>

pkg/sentry/fsimpl/proc/task_files.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,7 @@ func (s *statusFD) Generate(ctx context.Context, buf *bytes.Buffer) error {
10451045
fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
10461046
fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
10471047
fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps)
1048+
fmt.Fprintf(buf, "CapAmb:\t%016x\n", creds.AmbientCaps)
10481049
fmt.Fprintf(buf, "Seccomp:\t%d\n", s.task.SeccompMode())
10491050
// We unconditionally report a single NUMA node. See
10501051
// pkg/sentry/syscalls/linux/sys_mempolicy.go.

pkg/sentry/kernel/auth/capability_set.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ func ComputeCredsForExec(c *Credentials, f FilePrivileges, filename string,
234234
f.SetUserID = NoID
235235
f.SetGroupID = NoID
236236
}
237+
if noNewPrivs {
238+
f.HasCaps = false
239+
f.Effective = false
240+
}
237241
// "...if either the user or the group ID of the file has no mapping inside the namespace, the
238242
// set-user-ID (set-group-ID) bit is silently ignored: the new program is executed, but the
239243
// process's effective user (group) ID is left unchanged." - user_namespaces(7).
@@ -260,10 +264,16 @@ func ComputeCredsForExec(c *Credentials, f FilePrivileges, filename string,
260264
newC.EffectiveKGID = f.SetGroupID
261265
}
262266

263-
newC.PermittedCaps = CapabilitySet(0)
267+
fileIsPrivileged := f.SetUserID.Ok() || f.SetGroupID.Ok() || f.HasCaps
268+
ambientCaps := c.AmbientCaps
269+
if fileIsPrivileged {
270+
ambientCaps = 0
271+
}
272+
273+
newC.PermittedCaps = ambientCaps
264274
if f.HasCaps {
265-
// P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding))
266-
newC.PermittedCaps = (c.InheritableCaps & f.InheritableCaps) | (f.PermittedCaps & c.BoundingCaps)
275+
// P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient)
276+
newC.PermittedCaps |= (c.InheritableCaps & f.InheritableCaps) | (f.PermittedCaps & c.BoundingCaps)
267277

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

292302
// P'(effective) = effective ? P'(permitted) : P'(ambient).
293-
newC.EffectiveCaps = 0
303+
newC.EffectiveCaps = ambientCaps
294304
if f.Effective {
295305
newC.EffectiveCaps = newC.PermittedCaps
296306
}
297307

308+
newC.AmbientCaps = ambientCaps
309+
298310
// prctl(2): The "keep capabilities" value will be reset to 0 on subsequent calls to execve(2).
299311
newC.KeepCaps = false
300312

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

pkg/sentry/kernel/auth/capability_set_test.go

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import (
2222
)
2323

2424
// credentialsWithCaps creates a credentials object with the given capabilities.
25-
func credentialsWithCaps(inheritable, bounding CapabilitySet) *Credentials {
25+
func credentialsWithCaps(permitted, inheritable, bounding, ambient CapabilitySet) *Credentials {
2626
return NewUserCredentials(1001, 1001, nil, &TaskCapabilities{
27+
PermittedCaps: permitted,
2728
InheritableCaps: inheritable,
2829
BoundingCaps: bounding,
30+
AmbientCaps: ambient,
2931
}, NewRootUserNamespace())
3032
}
3133

@@ -72,7 +74,7 @@ func TestComputeCredsForExec(t *testing.T) {
7274
PermittedCaps: CapabilitySetOf(linux.CAP_NET_ADMIN),
7375
InheritableCaps: CapabilitySetOf(linux.CAP_NET_ADMIN),
7476
},
75-
creds: credentialsWithCaps(AllCapabilities, AllCapabilities),
77+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, 0),
7678
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
7779
wantEffective: true,
7880
},
@@ -84,7 +86,7 @@ func TestComputeCredsForExec(t *testing.T) {
8486
PermittedCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID}),
8587
InheritableCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETGID}),
8688
},
87-
creds: credentialsWithCaps(AllCapabilities, AllCapabilities),
89+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, 0),
8890
wantPermitted: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID, linux.CAP_SETGID}),
8991
wantEffective: true,
9092
},
@@ -96,7 +98,7 @@ func TestComputeCredsForExec(t *testing.T) {
9698
PermittedCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID}),
9799
InheritableCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETGID}),
98100
},
99-
creds: credentialsWithCaps(AllCapabilities, AllCapabilities),
101+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, 0),
100102
wantPermitted: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID, linux.CAP_SETGID}),
101103
wantEffective: false,
102104
},
@@ -108,9 +110,65 @@ func TestComputeCredsForExec(t *testing.T) {
108110
PermittedCaps: CapabilitySetOfMany([]linux.Capability{linux.CAP_CHOWN, linux.CAP_SETUID}),
109111
InheritableCaps: CapabilitySetOf(linux.CAP_CHOWN),
110112
},
111-
creds: credentialsWithCaps(AllCapabilities, CapabilitySetOf(linux.CAP_CHOWN)),
113+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_CHOWN), 0),
112114
wantErr: linuxerr.EPERM,
113115
},
116+
{
117+
name: "TestAmbientCapsPreservedNonPrivileged",
118+
filePrivs: FilePrivileges{
119+
HasCaps: false,
120+
Effective: false,
121+
},
122+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
123+
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
124+
wantEffective: true,
125+
},
126+
{
127+
name: "TestAmbientAndFileCapsCombined",
128+
filePrivs: FilePrivileges{
129+
HasCaps: true,
130+
Effective: false,
131+
PermittedCaps: CapabilitySetOf(linux.CAP_CHOWN),
132+
InheritableCaps: CapabilitySetOf(linux.CAP_CHOWN),
133+
},
134+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
135+
wantPermitted: CapabilitySetOf(linux.CAP_CHOWN),
136+
wantEffective: false,
137+
},
138+
{
139+
name: "TestAmbientCapsPreservedWithFileCapsAndNoNewPrivs",
140+
filePrivs: FilePrivileges{
141+
HasCaps: true,
142+
Effective: false,
143+
PermittedCaps: CapabilitySetOf(linux.CAP_CHOWN),
144+
InheritableCaps: CapabilitySetOf(linux.CAP_CHOWN),
145+
},
146+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
147+
noNewPrivs: true,
148+
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
149+
wantEffective: true,
150+
},
151+
{
152+
name: "TestAmbientCapsClearedWithSUIDNonRoot",
153+
filePrivs: FilePrivileges{
154+
SetUserID: KUID(1002),
155+
},
156+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
157+
allowSUID: true,
158+
wantPermitted: 0,
159+
wantEffective: false,
160+
},
161+
{
162+
name: "TestAmbientCapsPreservedWithSUIDAndNoNewPrivs",
163+
filePrivs: FilePrivileges{
164+
SetUserID: KUID(1002),
165+
},
166+
creds: credentialsWithCaps(AllCapabilities, AllCapabilities, AllCapabilities, CapabilitySetOf(linux.CAP_NET_ADMIN)),
167+
noNewPrivs: true,
168+
allowSUID: true,
169+
wantPermitted: CapabilitySetOf(linux.CAP_NET_ADMIN),
170+
wantEffective: true,
171+
},
114172
} {
115173
t.Run(tst.name, func(t *testing.T) {
116174
newC, _, err := ComputeCredsForExec(tst.creds, tst.filePrivs, "", tst.noNewPrivs, tst.stopPrivGain, tst.allowSUID)

pkg/sentry/kernel/auth/credentials.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type Credentials struct {
5050
InheritableCaps CapabilitySet
5151
EffectiveCaps CapabilitySet
5252
BoundingCaps CapabilitySet
53-
// Ambient capabilities are not introduced until Linux 4.3.
53+
AmbientCaps CapabilitySet
5454

5555
// KeepCaps is the flag for PR_SET_KEEPCAPS which allow capabilities to be
5656
// maintained after a switch from root user to non-root user via setuid().
@@ -127,7 +127,7 @@ func NewUserCredentials(kuid KUID, kgid KGID, extraKGIDs []KGID, capabilities *T
127127
creds.EffectiveCaps = capabilities.EffectiveCaps
128128
creds.BoundingCaps = capabilities.BoundingCaps
129129
creds.InheritableCaps = capabilities.InheritableCaps
130-
// TODO(gvisor.dev/issue/3166): Support ambient capabilities.
130+
creds.AmbientCaps = capabilities.AmbientCaps
131131
} else {
132132
// If no capabilities are specified, grant capabilities consistent with
133133
// setresuid + setresgid from NewRootCredentials to the given uid and
@@ -166,6 +166,7 @@ func (c *Credentials) ForkIntoUserNamespace(ns *UserNamespace) *Credentials {
166166
nc.InheritableCaps = 0
167167
nc.EffectiveCaps = AllCapabilities
168168
nc.BoundingCaps = AllCapabilities
169+
nc.AmbientCaps = 0
169170
// "A call to clone(2), unshare(2), or setns(2) using the CLONE_NEWUSER
170171
// flag sets the "securebits" flags (see capabilities(7)) to their default
171172
// values (all flags disabled) in the child (for clone(2)) or caller (for

pkg/sentry/kernel/task_identity.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func (t *Task) setKUIDsUnchecked(newR, newE, newS auth.KUID) {
174174
// nonzero value, then all capabilities are cleared from the permitted and
175175
// effective capability sets." - capabilities(7)
176176
if (oldR == root || oldE == root || oldS == root) && (newR != root && newE != root && newS != root) {
177+
creds.AmbientCaps = 0
177178
// prctl(2): "PR_SET_KEEPCAP: Set the state of the calling thread's
178179
// "keep capabilities" flag, which determines whether the thread's permitted
179180
// capability set is cleared when a change is made to the
@@ -405,6 +406,7 @@ func (t *Task) SetCapabilitySets(permitted, inheritable, effective auth.Capabili
405406
creds.PermittedCaps = permitted
406407
creds.InheritableCaps = inheritable
407408
creds.EffectiveCaps = effective
409+
creds.AmbientCaps &= permitted & inheritable
408410
t.creds.Store(creds)
409411
return nil
410412
}
@@ -448,3 +450,48 @@ func (t *Task) GetNoNewPrivs() bool {
448450
defer t.mu.Unlock()
449451
return t.noNewPrivs
450452
}
453+
454+
// AmbientCapability returns whether the capability cp is in the ambient set.
455+
func (t *Task) AmbientCapability(cp linux.Capability) (bool, error) {
456+
if !cp.Ok() {
457+
return false, linuxerr.EINVAL
458+
}
459+
return auth.CapabilitySetOf(cp)&t.Credentials().AmbientCaps != 0, nil
460+
}
461+
462+
// RaiseAmbientCapability adds capability cp to the ambient set.
463+
func (t *Task) RaiseAmbientCapability(cp linux.Capability) error {
464+
if !cp.Ok() {
465+
return linuxerr.EINVAL
466+
}
467+
creds := t.Credentials()
468+
cs := auth.CapabilitySetOf(cp)
469+
// "A capability can be added to the ambient set (PR_CAP_AMBIENT_RAISE) only
470+
// if it is already present in both the permitted and inheritable capability
471+
// sets of the thread." - capabilities(7)
472+
if cs&creds.PermittedCaps == 0 || cs&creds.InheritableCaps == 0 {
473+
return linuxerr.EPERM
474+
}
475+
creds = creds.Fork()
476+
creds.AmbientCaps |= cs
477+
t.creds.Store(creds)
478+
return nil
479+
}
480+
481+
// LowerAmbientCapability removes capability cp from the ambient set.
482+
func (t *Task) LowerAmbientCapability(cp linux.Capability) error {
483+
if !cp.Ok() {
484+
return linuxerr.EINVAL
485+
}
486+
creds := t.Credentials().Fork()
487+
creds.AmbientCaps &^= auth.CapabilitySetOf(cp)
488+
t.creds.Store(creds)
489+
return nil
490+
}
491+
492+
// ClearAmbientCapabilities removes all capabilities from the ambient set.
493+
func (t *Task) ClearAmbientCapabilities() {
494+
creds := t.Credentials().Fork()
495+
creds.AmbientCaps = 0
496+
t.creds.Store(creds)
497+
}

pkg/sentry/syscalls/linux/sys_prctl.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,63 @@ func Prctl(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
229229
}
230230
return 0, nil, t.DropBoundingCapability(cp)
231231

232+
case linux.PR_CAP_AMBIENT:
233+
suboption := args[1].Int()
234+
switch suboption {
235+
case linux.PR_CAP_AMBIENT_IS_SET:
236+
if args[3].Int() != 0 || args[4].Int() != 0 {
237+
return 0, nil, linuxerr.EINVAL
238+
}
239+
cp := linux.Capability(args[2].Uint64())
240+
if !cp.Ok() {
241+
return 0, nil, linuxerr.EINVAL
242+
}
243+
exist, err := t.AmbientCapability(cp)
244+
if err != nil {
245+
return 0, nil, err
246+
}
247+
if exist {
248+
return 1, nil, nil
249+
}
250+
return 0, nil, nil
251+
252+
case linux.PR_CAP_AMBIENT_RAISE:
253+
if args[3].Int() != 0 || args[4].Int() != 0 {
254+
return 0, nil, linuxerr.EINVAL
255+
}
256+
cp := linux.Capability(args[2].Uint64())
257+
if !cp.Ok() {
258+
return 0, nil, linuxerr.EINVAL
259+
}
260+
if err := t.RaiseAmbientCapability(cp); err != nil {
261+
return 0, nil, err
262+
}
263+
return 0, nil, nil
264+
265+
case linux.PR_CAP_AMBIENT_LOWER:
266+
if args[3].Int() != 0 || args[4].Int() != 0 {
267+
return 0, nil, linuxerr.EINVAL
268+
}
269+
cp := linux.Capability(args[2].Uint64())
270+
if !cp.Ok() {
271+
return 0, nil, linuxerr.EINVAL
272+
}
273+
if err := t.LowerAmbientCapability(cp); err != nil {
274+
return 0, nil, err
275+
}
276+
return 0, nil, nil
277+
278+
case linux.PR_CAP_AMBIENT_CLEAR_ALL:
279+
if args[2].Int() != 0 || args[3].Int() != 0 || args[4].Int() != 0 {
280+
return 0, nil, linuxerr.EINVAL
281+
}
282+
t.ClearAmbientCapabilities()
283+
return 0, nil, nil
284+
285+
default:
286+
return 0, nil, linuxerr.EINVAL
287+
}
288+
232289
case linux.PR_SET_CHILD_SUBREAPER:
233290
// "If arg2 is nonzero, set the "child subreaper" attribute of
234291
// the calling process; if arg2 is zero, unset the attribute."

test/syscalls/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ syscall_test(
6565
test = "//test/syscalls/linux:brk_test",
6666
)
6767

68+
syscall_test(
69+
test = "//test/syscalls/linux:capabilities_test",
70+
)
71+
6872
syscall_test(
6973
one_sandbox = False,
7074
test = "//test/syscalls/linux:cgroup_test",

test/syscalls/linux/BUILD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,22 @@ cc_library(
438438
alwayslink = 1,
439439
)
440440

441+
cc_binary(
442+
name = "capabilities_test",
443+
testonly = 1,
444+
srcs = ["capabilities.cc"],
445+
linkstatic = 1,
446+
malloc = "//test/util:errno_safe_allocator",
447+
deps = select_gtest() + [
448+
"//test/util:capability_util",
449+
"//test/util:posix_error",
450+
"//test/util:test_main",
451+
"//test/util:test_util",
452+
"//test/util:thread_util",
453+
"@com_google_absl//absl/time",
454+
],
455+
)
456+
441457
cc_binary(
442458
name = "chdir_test",
443459
testonly = 1,

0 commit comments

Comments
 (0)