Skip to content

Commit 7962dae

Browse files
authored
Add capabilities support (#444)
Closes #442 This adds capabilities support to LinuxContainer via a new surface in ContainerizationOS + some C wrappers.
1 parent 96d37e2 commit 7962dae

File tree

13 files changed

+1207
-10
lines changed

13 files changed

+1207
-10
lines changed

Sources/CShim/capability.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#if defined(__linux__)
18+
19+
#include <sys/syscall.h>
20+
#include <unistd.h>
21+
#include "capability.h"
22+
23+
// Capability syscall wrappers
24+
int CZ_capget(void *header, void *data) {
25+
return syscall(SYS_capget, header, data);
26+
}
27+
28+
int CZ_capset(void *header, void *data) {
29+
return syscall(SYS_capset, header, data);
30+
}
31+
32+
#endif

Sources/CShim/include/capability.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef __CAPABILITY_H
18+
#define __CAPABILITY_H
19+
20+
#if defined(__linux__)
21+
22+
// Capability syscall wrappers
23+
int CZ_capget(void *header, void *data);
24+
int CZ_capset(void *header, void *data);
25+
26+
#endif
27+
28+
#endif

Sources/CShim/include/prctl.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef __PRCTL_H
18+
#define __PRCTL_H
19+
20+
#if defined(__linux__)
21+
22+
#include <sys/types.h>
23+
24+
// Capability management prctl wrappers
25+
int CZ_prctl_set_keepcaps();
26+
int CZ_prctl_clear_keepcaps();
27+
int CZ_prctl_capbset_drop(unsigned int capability);
28+
int CZ_prctl_cap_ambient_clear_all();
29+
int CZ_prctl_cap_ambient_raise(unsigned int capability);
30+
31+
#endif
32+
33+
#endif

Sources/CShim/prctl.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#if defined(__linux__)
18+
19+
#include <sys/prctl.h>
20+
#include "prctl.h"
21+
22+
// Set keep caps to preserve capabilities across setuid()
23+
int CZ_prctl_set_keepcaps() {
24+
return prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
25+
}
26+
27+
// Clear keep caps after user change
28+
int CZ_prctl_clear_keepcaps() {
29+
return prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0);
30+
}
31+
32+
// Drop capability from bounding set
33+
int CZ_prctl_capbset_drop(unsigned int capability) {
34+
return prctl(PR_CAPBSET_DROP, capability, 0, 0, 0);
35+
}
36+
37+
// Clear all ambient capabilities
38+
int CZ_prctl_cap_ambient_clear_all() {
39+
return prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
40+
}
41+
42+
// Raise ambient capability
43+
int CZ_prctl_cap_ambient_raise(unsigned int capability) {
44+
return prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, capability, 0, 0);
45+
}
46+
47+
#endif

Sources/Containerization/LinuxProcessConfiguration.swift

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,116 @@
1717
import ContainerizationOCI
1818
import ContainerizationOS
1919

20+
/// User-friendly Linux capabilities configuration
21+
public struct LinuxCapabilities: Sendable {
22+
/// Capabilities that define the maximum set of capabilities a process can have
23+
public var bounding: [CapabilityName] = []
24+
/// Capabilities that are actually in effect for the current process
25+
public var effective: [CapabilityName] = []
26+
/// Capabilities that can be inherited by child processes
27+
public var inheritable: [CapabilityName] = []
28+
/// Capabilities that are currently permitted for the process
29+
public var permitted: [CapabilityName] = []
30+
/// Capabilities that are preserved across execve() calls
31+
public var ambient: [CapabilityName] = []
32+
33+
/// Grant all capabilities
34+
public static let allCapabilities = LinuxCapabilities(
35+
bounding: CapabilityName.allCases,
36+
effective: CapabilityName.allCases,
37+
inheritable: CapabilityName.allCases,
38+
permitted: CapabilityName.allCases,
39+
ambient: CapabilityName.allCases
40+
)
41+
42+
/// Default configuration
43+
public static let defaultOCICapabilities = LinuxCapabilities(
44+
bounding: [
45+
.chown,
46+
.dacOverride,
47+
.fsetid,
48+
.fowner,
49+
.mknod,
50+
.netRaw,
51+
.setgid,
52+
.setuid,
53+
.setfcap,
54+
.setpcap,
55+
.netBindService,
56+
.sysChroot,
57+
.kill,
58+
.auditWrite,
59+
],
60+
effective: [
61+
.chown,
62+
.dacOverride,
63+
.fsetid,
64+
.fowner,
65+
.mknod,
66+
.netRaw,
67+
.setgid,
68+
.setuid,
69+
.setfcap,
70+
.setpcap,
71+
.netBindService,
72+
.sysChroot,
73+
.kill,
74+
.auditWrite,
75+
],
76+
permitted: [
77+
.chown,
78+
.dacOverride,
79+
.fsetid,
80+
.fowner,
81+
.mknod,
82+
.netRaw,
83+
.setgid,
84+
.setuid,
85+
.setfcap,
86+
.setpcap,
87+
.netBindService,
88+
.sysChroot,
89+
.kill,
90+
.auditWrite,
91+
],
92+
)
93+
94+
public init(
95+
bounding: [CapabilityName] = [],
96+
effective: [CapabilityName] = [],
97+
inheritable: [CapabilityName] = [],
98+
permitted: [CapabilityName] = [],
99+
ambient: [CapabilityName] = []
100+
) {
101+
self.bounding = bounding
102+
self.effective = effective
103+
self.inheritable = inheritable
104+
self.permitted = permitted
105+
self.ambient = ambient
106+
}
107+
108+
/// Convenience initializer that sets the same capabilities to effective, permitted, and bounding sets
109+
/// This matches the typical pattern used by containerd/runc
110+
public init(capabilities: [CapabilityName]) {
111+
self.bounding = capabilities
112+
self.effective = capabilities
113+
self.inheritable = []
114+
self.permitted = capabilities
115+
self.ambient = []
116+
}
117+
118+
/// Convert to OCI format for transport
119+
public func toOCI() -> ContainerizationOCI.LinuxCapabilities {
120+
ContainerizationOCI.LinuxCapabilities(
121+
bounding: bounding.isEmpty ? nil : bounding.map { $0.description },
122+
effective: effective.isEmpty ? nil : effective.map { $0.description },
123+
inheritable: inheritable.isEmpty ? nil : inheritable.map { $0.description },
124+
permitted: permitted.isEmpty ? nil : permitted.map { $0.description },
125+
ambient: ambient.isEmpty ? nil : ambient.map { $0.description }
126+
)
127+
}
128+
}
129+
20130
public struct LinuxProcessConfiguration: Sendable {
21131
/// The default PATH value for a process.
22132
public static let defaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
@@ -31,6 +141,8 @@ public struct LinuxProcessConfiguration: Sendable {
31141
public var user: ContainerizationOCI.User = .init()
32142
/// The rlimits for the container process.
33143
public var rlimits: [POSIXRlimit] = []
144+
/// The Linux capabilities for the container process.
145+
public var capabilities: LinuxCapabilities = .allCapabilities
34146
/// Whether to allocate a pseudo terminal for the process. If you'd like interactive
35147
/// behavior and are planning to use a terminal for stdin/out/err on the client side,
36148
/// this should likely be set to true.
@@ -50,6 +162,7 @@ public struct LinuxProcessConfiguration: Sendable {
50162
workingDirectory: String = "/",
51163
user: ContainerizationOCI.User = .init(),
52164
rlimits: [POSIXRlimit] = [],
165+
capabilities: LinuxCapabilities = .allCapabilities,
53166
terminal: Bool = false,
54167
stdin: ReaderStream? = nil,
55168
stdout: Writer? = nil,
@@ -60,6 +173,7 @@ public struct LinuxProcessConfiguration: Sendable {
60173
self.workingDirectory = workingDirectory
61174
self.user = user
62175
self.rlimits = rlimits
176+
self.capabilities = capabilities
63177
self.terminal = terminal
64178
self.stdin = stdin
65179
self.stdout = stdout
@@ -92,6 +206,7 @@ public struct LinuxProcessConfiguration: Sendable {
92206
args: self.arguments,
93207
cwd: self.workingDirectory,
94208
env: self.environmentVariables,
209+
capabilities: self.capabilities.toOCI(),
95210
user: self.user,
96211
rlimits: self.rlimits,
97212
terminal: self.terminal

Sources/ContainerizationOCI/Spec.swift

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ public struct Process: Codable, Sendable {
157157
}()
158158
self.init(args: args, cwd: cwd, env: env, user: user)
159159
}
160+
160161
public init(from decoder: Decoder) throws {
161162
self.init()
162163

@@ -194,25 +195,51 @@ public struct Process: Codable, Sendable {
194195
}
195196

196197
public struct LinuxCapabilities: Codable, Sendable {
197-
public var bounding: [String]
198-
public var effective: [String]
199-
public var inheritable: [String]
200-
public var permitted: [String]
201-
public var ambient: [String]
198+
public var bounding: [String]?
199+
public var effective: [String]?
200+
public var inheritable: [String]?
201+
public var permitted: [String]?
202+
public var ambient: [String]?
203+
204+
enum CodingKeys: String, CodingKey {
205+
case bounding
206+
case effective
207+
case inheritable
208+
case permitted
209+
case ambient
210+
}
202211

203212
public init(
204-
bounding: [String],
205-
effective: [String],
206-
inheritable: [String],
207-
permitted: [String],
208-
ambient: [String]
213+
bounding: [String]? = nil,
214+
effective: [String]? = nil,
215+
inheritable: [String]? = nil,
216+
permitted: [String]? = nil,
217+
ambient: [String]? = nil
209218
) {
210219
self.bounding = bounding
211220
self.effective = effective
212221
self.inheritable = inheritable
213222
self.permitted = permitted
214223
self.ambient = ambient
215224
}
225+
226+
public init(from decoder: Decoder) throws {
227+
let container = try decoder.container(keyedBy: CodingKeys.self)
228+
self.bounding = try container.decodeIfPresent([String].self, forKey: .bounding)
229+
self.effective = try container.decodeIfPresent([String].self, forKey: .effective)
230+
self.inheritable = try container.decodeIfPresent([String].self, forKey: .inheritable)
231+
self.permitted = try container.decodeIfPresent([String].self, forKey: .permitted)
232+
self.ambient = try container.decodeIfPresent([String].self, forKey: .ambient)
233+
}
234+
235+
public func encode(to encoder: Encoder) throws {
236+
var container = encoder.container(keyedBy: CodingKeys.self)
237+
try container.encodeIfPresent(bounding, forKey: .bounding)
238+
try container.encodeIfPresent(effective, forKey: .effective)
239+
try container.encodeIfPresent(inheritable, forKey: .inheritable)
240+
try container.encodeIfPresent(permitted, forKey: .permitted)
241+
try container.encodeIfPresent(ambient, forKey: .ambient)
242+
}
216243
}
217244

218245
public struct Box: Codable, Sendable {

0 commit comments

Comments
 (0)