jail: read and apply umask from OCI if defined
[project/procd.git] / jail / capabilities.c
1 /*
2 * Copyright (C) 2015 Etienne CHAMPETIER <champetier.etienne@gmail.com>
3 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License version 2.1
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15 #define _GNU_SOURCE 1
16 #include <syslog.h>
17 #include <sys/prctl.h>
18 #include <libubox/blobmsg.h>
19 #include <libubox/blobmsg_json.h>
20
21 #include "log.h"
22 #include "../capabilities-names.h"
23 #include "capabilities.h"
24
25 #define JAIL_CAP_ERROR (1LLU << (CAP_LAST_CAP+1))
26 #define JAIL_CAP_ALL (0xffffffffffffffffLLU)
27
28 static int find_capabilities(const char *name)
29 {
30 int i;
31
32 for (i = 0; i <= CAP_LAST_CAP; i++)
33 if (capabilities_names[i] && !strcasecmp(capabilities_names[i], name))
34 return i;
35
36 return -1;
37 }
38
39 enum {
40 OCI_CAPABILITIES_BOUNDING,
41 OCI_CAPABILITIES_EFFECTIVE,
42 OCI_CAPABILITIES_INHERITABLE,
43 OCI_CAPABILITIES_PERMITTED,
44 OCI_CAPABILITIES_AMBIENT,
45 __OCI_CAPABILITIES_MAX
46 };
47
48 static const struct blobmsg_policy oci_capabilities_policy[] = {
49 [OCI_CAPABILITIES_BOUNDING] = { "bounding", BLOBMSG_TYPE_ARRAY },
50 [OCI_CAPABILITIES_EFFECTIVE] = { "effective", BLOBMSG_TYPE_ARRAY },
51 [OCI_CAPABILITIES_INHERITABLE] = { "inheritable", BLOBMSG_TYPE_ARRAY },
52 [OCI_CAPABILITIES_PERMITTED] = { "permitted", BLOBMSG_TYPE_ARRAY },
53 [OCI_CAPABILITIES_AMBIENT] = { "ambient", BLOBMSG_TYPE_ARRAY },
54 };
55
56 static uint64_t parseOCIcap(struct blob_attr *msg)
57 {
58 struct blob_attr *cur;
59 int rem;
60 uint64_t caps = 0;
61 int capnum;
62
63 /* each capset is optional, set all-1 mask if absent */
64 if (!msg)
65 return JAIL_CAP_ALL;
66
67 blobmsg_for_each_attr(cur, msg, rem) {
68 capnum = find_capabilities(blobmsg_get_string(cur));
69 if (capnum < 0)
70 return JAIL_CAP_ERROR;
71
72 caps |= (1LLU << capnum);
73 }
74
75 return caps;
76 }
77
78 int parseOCIcapabilities(struct jail_capset *capset, struct blob_attr *msg)
79 {
80 struct blob_attr *tb[__OCI_CAPABILITIES_MAX];
81 uint64_t caps;
82 blobmsg_parse(oci_capabilities_policy, __OCI_CAPABILITIES_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
83
84 caps = parseOCIcap(tb[OCI_CAPABILITIES_BOUNDING]);
85 if (caps == JAIL_CAP_ERROR)
86 return EINVAL;
87 else
88 capset->bounding = caps;
89
90 caps = parseOCIcap(tb[OCI_CAPABILITIES_EFFECTIVE]);
91 if (caps == JAIL_CAP_ERROR)
92 return EINVAL;
93 else
94 capset->effective = caps;
95
96 caps = parseOCIcap(tb[OCI_CAPABILITIES_INHERITABLE]);
97 if (caps == JAIL_CAP_ERROR)
98 return EINVAL;
99 else
100 capset->inheritable = caps;
101
102 caps = parseOCIcap(tb[OCI_CAPABILITIES_PERMITTED]);
103 if (caps == JAIL_CAP_ERROR)
104 return EINVAL;
105 else
106 capset->permitted = caps;
107
108 caps = parseOCIcap(tb[OCI_CAPABILITIES_AMBIENT]);
109 if (caps == JAIL_CAP_ERROR)
110 return EINVAL;
111 else
112 capset->ambient = caps;
113
114 capset->apply = 1;
115
116 return 0;
117 }
118
119
120 int applyOCIcapabilities(struct jail_capset ocicapset)
121 {
122 struct __user_cap_header_struct uh = {};
123 struct __user_cap_data_struct ud;
124 int cap;
125 int is_set;
126
127 if (!ocicapset.apply)
128 return 0;
129
130 /* drop from bounding set */
131 if (ocicapset.bounding != JAIL_CAP_ALL) {
132 for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
133 if (!prctl(PR_CAPBSET_READ, cap, 0, 0, 0)) {
134 /* can't raise */
135 if (ocicapset.bounding & (1LLU << cap))
136 ERROR("capability %s (%d) is not in bounding set\n", capabilities_names[cap], cap);
137
138 continue;
139 }
140 if ( (ocicapset.bounding & (1LLU << cap)) == 0) {
141 DEBUG("dropping capability %s (%d) from bounding set\n", capabilities_names[cap], cap);
142 if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) {
143 ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %m\n", cap);
144 return errno;
145 }
146 } else {
147 DEBUG("keeping capability %s (%d)\n", capabilities_names[cap], cap);
148 }
149 }
150 }
151
152 /* set effective, permitted and inheritable */
153 uh.version = _LINUX_CAPABILITY_VERSION_3;
154 uh.pid = getpid();
155
156 if (capget(&uh, &ud)) {
157 ERROR("capget() failed\n");
158 return -1;
159 }
160
161 DEBUG("old capabilities: Pe=%08x Pp=%08x Pi=%08x\n", ud.effective, ud.permitted, ud.inheritable);
162
163 if (ocicapset.effective != JAIL_CAP_ALL)
164 ud.effective = ocicapset.effective;
165
166 if (ocicapset.permitted != JAIL_CAP_ALL)
167 ud.permitted = ocicapset.permitted;
168
169 if (ocicapset.inheritable != JAIL_CAP_ALL)
170 ud.inheritable = ocicapset.inheritable;
171
172 DEBUG("new capabilities: Pe=%08x Pp=%08x Pi=%08x\n", ud.effective, ud.permitted, ud.inheritable);
173
174 if (capset(&uh, &ud)) {
175 ERROR("capset() failed\n");
176 return -1;
177 }
178
179 /* edit ambient set */
180 if (ocicapset.ambient != JAIL_CAP_ALL) {
181 for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
182 is_set = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, cap, 0, 0);
183 if ( (ocicapset.ambient & (1LLU << cap)) == 0) {
184 if (is_set) {
185 DEBUG("dropping capability %s (%d) from ambient set\n", capabilities_names[cap], cap);
186 if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_LOWER, cap, 0, 0)) {
187 ERROR("prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_LOWER, %d, 0, 0) failed: %m\n", cap);
188 return errno;
189 }
190 }
191 } else {
192 if (!is_set) {
193 DEBUG("raising capability %s (%d) to ambient set\n", capabilities_names[cap], cap);
194 if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) {\
195 ERROR("prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, %d, 0, 0) failed: %m\n", cap);
196 return errno;
197 }
198 }
199 }
200 }
201 }
202
203 return 0;
204 }
205
206 int drop_capabilities(const char *file)
207 {
208 enum {
209 CAP_KEEP,
210 CAP_DROP,
211 __CAP_MAX
212 };
213 static const struct blobmsg_policy policy[__CAP_MAX] = {
214 [CAP_KEEP] = { .name = "cap.keep", .type = BLOBMSG_TYPE_ARRAY },
215 [CAP_DROP] = { .name = "cap.drop", .type = BLOBMSG_TYPE_ARRAY },
216 };
217 struct blob_buf b = { 0 };
218 struct blob_attr *tb[__CAP_MAX];
219 struct blob_attr *cur;
220 int rem, cap;
221 char *name;
222 uint64_t capdrop = 0LLU;
223
224 DEBUG("dropping capabilities\n");
225
226 blob_buf_init(&b, 0);
227 if (!blobmsg_add_json_from_file(&b, file)) {
228 ERROR("failed to load %s\n", file);
229 return -1;
230 }
231
232 blobmsg_parse(policy, __CAP_MAX, tb, blob_data(b.head), blob_len(b.head));
233 if (!tb[CAP_KEEP] && !tb[CAP_DROP]) {
234 ERROR("failed to parse %s\n", file);
235 return -1;
236 }
237
238 blobmsg_for_each_attr(cur, tb[CAP_KEEP], rem) {
239 name = blobmsg_get_string(cur);
240 if (!name) {
241 ERROR("invalid capability name in cap.keep\n");
242 return -1;
243 }
244 cap = find_capabilities(name);
245 if (cap == -1) {
246 ERROR("unknown capability %s in cap.keep\n", name);
247 return -1;
248 }
249 capdrop |= (1LLU << cap);
250 }
251
252 if (capdrop == 0LLU) {
253 DEBUG("cap.keep empty -> only dropping capabilities from cap.drop (blacklist)\n");
254 capdrop = JAIL_CAP_ALL;
255 } else {
256 DEBUG("cap.keep has at least one capability -> dropping every capabilities not in cap.keep (whitelist)\n");
257 }
258
259 blobmsg_for_each_attr(cur, tb[CAP_DROP], rem) {
260 name = blobmsg_get_string(cur);
261 if (!name) {
262 ERROR("invalid capability name in cap.drop\n");
263 return -1;
264 }
265 cap = find_capabilities(name);
266 if (cap == -1) {
267 ERROR("unknown capability %s in cap.drop\n", name);
268 return -1;
269 }
270 capdrop &= ~(1LLU << cap);
271 }
272
273 for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
274 if ( (capdrop & (1LLU << cap)) == 0) {
275 DEBUG("dropping capability %s (%d)\n", capabilities_names[cap], cap);
276 if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) {
277 ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %m\n", cap);
278 return errno;
279 }
280 } else {
281 DEBUG("keeping capability %s (%d)\n", capabilities_names[cap], cap);
282 }
283 }
284
285 return 0;
286 }