]>
Commit | Line | Data |
---|---|---|
d7832d2c KS |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2010 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
5430f7f2 LP |
9 | under the terms of the GNU Lesser General Public License as published by |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
d7832d2c KS |
11 | (at your option) any later version. |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
5430f7f2 | 16 | Lesser General Public License for more details. |
d7832d2c | 17 | |
5430f7f2 | 18 | You should have received a copy of the GNU Lesser General Public License |
d7832d2c KS |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. |
20 | ***/ | |
21 | ||
d7832d2c | 22 | #include <errno.h> |
3ffd4af2 | 23 | #include <grp.h> |
d7832d2c | 24 | #include <stdio.h> |
d7832d2c KS |
25 | #include <sys/capability.h> |
26 | #include <sys/prctl.h> | |
3ffd4af2 | 27 | #include <unistd.h> |
d7832d2c | 28 | |
3ffd4af2 LP |
29 | #include "capability.h" |
30 | #include "fileio.h" | |
31 | #include "log.h" | |
d7832d2c | 32 | #include "macro.h" |
6bedfcbb | 33 | #include "parse-util.h" |
d7832d2c | 34 | #include "util.h" |
d7832d2c KS |
35 | |
36 | int have_effective_cap(int value) { | |
5ce70e5b | 37 | _cleanup_cap_free_ cap_t cap; |
d7832d2c | 38 | cap_flag_value_t fv; |
d7832d2c | 39 | |
ec8927ca LP |
40 | cap = cap_get_proc(); |
41 | if (!cap) | |
d7832d2c KS |
42 | return -errno; |
43 | ||
44 | if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) | |
5ce70e5b | 45 | return -errno; |
d7832d2c | 46 | else |
5ce70e5b | 47 | return fv == CAP_SET; |
d7832d2c KS |
48 | } |
49 | ||
50 | unsigned long cap_last_cap(void) { | |
ec202eae SL |
51 | static thread_local unsigned long saved; |
52 | static thread_local bool valid = false; | |
80b43783 | 53 | _cleanup_free_ char *content = NULL; |
a7f7d1bd | 54 | unsigned long p = 0; |
80b43783 | 55 | int r; |
d7832d2c KS |
56 | |
57 | if (valid) | |
58 | return saved; | |
59 | ||
80b43783 DH |
60 | /* available since linux-3.2 */ |
61 | r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); | |
62 | if (r >= 0) { | |
63 | r = safe_atolu(content, &p); | |
64 | if (r >= 0) { | |
65 | saved = p; | |
66 | valid = true; | |
67 | return p; | |
68 | } | |
69 | } | |
70 | ||
71 | /* fall back to syscall-probing for pre linux-3.2 */ | |
d7832d2c KS |
72 | p = (unsigned long) CAP_LAST_CAP; |
73 | ||
74 | if (prctl(PR_CAPBSET_READ, p) < 0) { | |
75 | ||
76 | /* Hmm, look downwards, until we find one that | |
77 | * works */ | |
78 | for (p--; p > 0; p --) | |
79 | if (prctl(PR_CAPBSET_READ, p) >= 0) | |
80 | break; | |
81 | ||
82 | } else { | |
83 | ||
84 | /* Hmm, look upwards, until we find one that doesn't | |
85 | * work */ | |
86 | for (;; p++) | |
87 | if (prctl(PR_CAPBSET_READ, p+1) < 0) | |
88 | break; | |
89 | } | |
90 | ||
91 | saved = p; | |
92 | valid = true; | |
93 | ||
94 | return p; | |
95 | } | |
ec8927ca LP |
96 | |
97 | int capability_bounding_set_drop(uint64_t drop, bool right_now) { | |
6a010ac9 | 98 | _cleanup_cap_free_ cap_t after_cap = NULL; |
ec8927ca | 99 | cap_flag_value_t fv; |
6a010ac9 | 100 | unsigned long i; |
ec8927ca LP |
101 | int r; |
102 | ||
103 | /* If we are run as PID 1 we will lack CAP_SETPCAP by default | |
104 | * in the effective set (yes, the kernel drops that when | |
105 | * executing init!), so get it back temporarily so that we can | |
106 | * call PR_CAPBSET_DROP. */ | |
107 | ||
108 | after_cap = cap_get_proc(); | |
109 | if (!after_cap) | |
110 | return -errno; | |
111 | ||
5ce70e5b | 112 | if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) |
ec8927ca | 113 | return -errno; |
ec8927ca LP |
114 | |
115 | if (fv != CAP_SET) { | |
6a010ac9 | 116 | _cleanup_cap_free_ cap_t temp_cap = NULL; |
ec8927ca LP |
117 | static const cap_value_t v = CAP_SETPCAP; |
118 | ||
119 | temp_cap = cap_dup(after_cap); | |
120 | if (!temp_cap) { | |
121 | r = -errno; | |
122 | goto finish; | |
123 | } | |
124 | ||
125 | if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { | |
126 | r = -errno; | |
127 | goto finish; | |
128 | } | |
129 | ||
130 | if (cap_set_proc(temp_cap) < 0) { | |
131 | r = -errno; | |
132 | goto finish; | |
133 | } | |
134 | } | |
135 | ||
136 | for (i = 0; i <= cap_last_cap(); i++) { | |
137 | ||
138 | if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { | |
139 | cap_value_t v; | |
140 | ||
141 | /* Drop it from the bounding set */ | |
142 | if (prctl(PR_CAPBSET_DROP, i) < 0) { | |
143 | r = -errno; | |
144 | goto finish; | |
145 | } | |
693eb9a2 | 146 | v = (cap_value_t) i; |
ec8927ca LP |
147 | |
148 | /* Also drop it from the inheritable set, so | |
149 | * that anything we exec() loses the | |
150 | * capability for good. */ | |
151 | if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { | |
152 | r = -errno; | |
153 | goto finish; | |
154 | } | |
155 | ||
156 | /* If we shall apply this right now drop it | |
157 | * also from our own capability sets. */ | |
158 | if (right_now) { | |
159 | if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || | |
160 | cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { | |
161 | r = -errno; | |
162 | goto finish; | |
163 | } | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
168 | r = 0; | |
169 | ||
170 | finish: | |
a349eb10 LP |
171 | if (cap_set_proc(after_cap) < 0) |
172 | return -errno; | |
ec8927ca LP |
173 | |
174 | return r; | |
175 | } | |
939b8f14 LP |
176 | |
177 | static int drop_from_file(const char *fn, uint64_t drop) { | |
178 | int r, k; | |
179 | uint32_t hi, lo; | |
180 | uint64_t current, after; | |
181 | char *p; | |
182 | ||
183 | r = read_one_line_file(fn, &p); | |
184 | if (r < 0) | |
185 | return r; | |
186 | ||
187 | assert_cc(sizeof(hi) == sizeof(unsigned)); | |
188 | assert_cc(sizeof(lo) == sizeof(unsigned)); | |
189 | ||
190 | k = sscanf(p, "%u %u", &lo, &hi); | |
191 | free(p); | |
192 | ||
193 | if (k != 2) | |
194 | return -EIO; | |
195 | ||
196 | current = (uint64_t) lo | ((uint64_t) hi << 32ULL); | |
197 | after = current & ~drop; | |
198 | ||
199 | if (current == after) | |
200 | return 0; | |
201 | ||
202 | lo = (unsigned) (after & 0xFFFFFFFFULL); | |
203 | hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL); | |
204 | ||
205 | if (asprintf(&p, "%u %u", lo, hi) < 0) | |
206 | return -ENOMEM; | |
207 | ||
4c1fc3e4 | 208 | r = write_string_file(fn, p, WRITE_STRING_FILE_CREATE); |
939b8f14 LP |
209 | free(p); |
210 | ||
211 | return r; | |
212 | } | |
213 | ||
214 | int capability_bounding_set_drop_usermode(uint64_t drop) { | |
215 | int r; | |
216 | ||
217 | r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop); | |
218 | if (r < 0) | |
219 | return r; | |
220 | ||
221 | r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop); | |
222 | if (r < 0) | |
223 | return r; | |
224 | ||
225 | return r; | |
226 | } | |
966bff26 | 227 | |
ed617ec2 | 228 | int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { |
966bff26 | 229 | _cleanup_cap_free_ cap_t d = NULL; |
f11943c5 | 230 | unsigned i, j = 0; |
966bff26 LP |
231 | int r; |
232 | ||
233 | /* Unfortunately we cannot leave privilege dropping to PID 1 | |
234 | * here, since we want to run as user but want to keep some | |
235 | * capabilities. Since file capabilities have been introduced | |
236 | * this cannot be done across exec() anymore, unless our | |
237 | * binary has the capability configured in the file system, | |
238 | * which we want to avoid. */ | |
239 | ||
4a62c710 MS |
240 | if (setresgid(gid, gid, gid) < 0) |
241 | return log_error_errno(errno, "Failed to change group ID: %m"); | |
966bff26 | 242 | |
4a62c710 MS |
243 | if (setgroups(0, NULL) < 0) |
244 | return log_error_errno(errno, "Failed to drop auxiliary groups list: %m"); | |
966bff26 | 245 | |
51ddf615 ZJS |
246 | /* Ensure we keep the permitted caps across the setresuid() */ |
247 | if (prctl(PR_SET_KEEPCAPS, 1) < 0) | |
4a62c710 | 248 | return log_error_errno(errno, "Failed to enable keep capabilities flag: %m"); |
966bff26 LP |
249 | |
250 | r = setresuid(uid, uid, uid); | |
4a62c710 MS |
251 | if (r < 0) |
252 | return log_error_errno(errno, "Failed to change user ID: %m"); | |
966bff26 | 253 | |
4a62c710 MS |
254 | if (prctl(PR_SET_KEEPCAPS, 0) < 0) |
255 | return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); | |
966bff26 | 256 | |
f11943c5 | 257 | /* Drop all caps from the bounding set, except the ones we want */ |
ed617ec2 | 258 | r = capability_bounding_set_drop(~keep_capabilities, true); |
f647962d MS |
259 | if (r < 0) |
260 | return log_error_errno(r, "Failed to drop capabilities: %m"); | |
966bff26 | 261 | |
f11943c5 | 262 | /* Now upgrade the permitted caps we still kept to effective caps */ |
966bff26 LP |
263 | d = cap_init(); |
264 | if (!d) | |
265 | return log_oom(); | |
266 | ||
51ddf615 | 267 | if (keep_capabilities) { |
057255fb | 268 | cap_value_t bits[u64log2(keep_capabilities) + 1]; |
6a010ac9 | 269 | |
7d328b54 | 270 | for (i = 0; i < ELEMENTSOF(bits); i++) |
51ddf615 ZJS |
271 | if (keep_capabilities & (1ULL << i)) |
272 | bits[j++] = i; | |
057255fb | 273 | |
2c9fc266 ZJS |
274 | /* use enough bits */ |
275 | assert(i == 64 || (keep_capabilities >> i) == 0); | |
276 | /* don't use too many bits */ | |
277 | assert(keep_capabilities & (1ULL << (i - 1))); | |
966bff26 | 278 | |
51ddf615 ZJS |
279 | if (cap_set_flag(d, CAP_EFFECTIVE, j, bits, CAP_SET) < 0 || |
280 | cap_set_flag(d, CAP_PERMITTED, j, bits, CAP_SET) < 0) { | |
281 | log_error_errno(errno, "Failed to enable capabilities bits: %m"); | |
282 | return -errno; | |
283 | } | |
284 | ||
285 | if (cap_set_proc(d) < 0) | |
286 | return log_error_errno(errno, "Failed to increase capabilities: %m"); | |
287 | } | |
966bff26 LP |
288 | |
289 | return 0; | |
290 | } | |
dd5ae4c3 PK |
291 | |
292 | int drop_capability(cap_value_t cv) { | |
293 | _cleanup_cap_free_ cap_t tmp_cap = NULL; | |
294 | ||
295 | tmp_cap = cap_get_proc(); | |
296 | if (!tmp_cap) | |
297 | return -errno; | |
298 | ||
299 | if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, CAP_CLEAR) < 0) || | |
300 | (cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, CAP_CLEAR) < 0) || | |
301 | (cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, CAP_CLEAR) < 0)) | |
302 | return -errno; | |
303 | ||
304 | if (cap_set_proc(tmp_cap) < 0) | |
305 | return -errno; | |
306 | ||
307 | return 0; | |
308 | } |