Commit | Line | Data |
---|---|---|
b886d83c | 1 | // SPDX-License-Identifier: GPL-2.0-only |
cff281f6 JJ |
2 | /* |
3 | * AppArmor security module | |
4 | * | |
5 | * This file contains AppArmor policy manipulation functions | |
6 | * | |
7 | * Copyright (C) 1998-2008 Novell/SUSE | |
8 | * Copyright 2009-2017 Canonical Ltd. | |
9 | * | |
cff281f6 JJ |
10 | * AppArmor policy namespaces, allow for different sets of policies |
11 | * to be loaded for tasks within the namespace. | |
12 | */ | |
13 | ||
14 | #include <linux/list.h> | |
15 | #include <linux/mutex.h> | |
16 | #include <linux/slab.h> | |
17 | #include <linux/string.h> | |
18 | ||
19 | #include "include/apparmor.h" | |
d8889d49 | 20 | #include "include/cred.h" |
cff281f6 | 21 | #include "include/policy_ns.h" |
637f688d | 22 | #include "include/label.h" |
cff281f6 JJ |
23 | #include "include/policy.h" |
24 | ||
25 | /* root profile namespace */ | |
98849dff | 26 | struct aa_ns *root_ns; |
cff281f6 JJ |
27 | const char *aa_hidden_ns_name = "---"; |
28 | ||
29 | /** | |
30 | * aa_ns_visible - test if @view is visible from @curr | |
31 | * @curr: namespace to treat as the parent (NOT NULL) | |
32 | * @view: namespace to test if visible from @curr (NOT NULL) | |
92b6d8ef | 33 | * @subns: whether view of a subns is allowed |
cff281f6 JJ |
34 | * |
35 | * Returns: true if @view is visible from @curr else false | |
36 | */ | |
92b6d8ef | 37 | bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns) |
cff281f6 JJ |
38 | { |
39 | if (curr == view) | |
40 | return true; | |
41 | ||
92b6d8ef JJ |
42 | if (!subns) |
43 | return false; | |
44 | ||
cff281f6 JJ |
45 | for ( ; view; view = view->parent) { |
46 | if (view->parent == curr) | |
47 | return true; | |
48 | } | |
92b6d8ef | 49 | |
cff281f6 JJ |
50 | return false; |
51 | } | |
52 | ||
53 | /** | |
54 | * aa_na_name - Find the ns name to display for @view from @curr | |
55 | * @curr - current namespace (NOT NULL) | |
56 | * @view - namespace attempting to view (NOT NULL) | |
92b6d8ef | 57 | * @subns - are subns visible |
cff281f6 JJ |
58 | * |
59 | * Returns: name of @view visible from @curr | |
60 | */ | |
92b6d8ef | 61 | const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns) |
cff281f6 JJ |
62 | { |
63 | /* if view == curr then the namespace name isn't displayed */ | |
64 | if (curr == view) | |
65 | return ""; | |
66 | ||
92b6d8ef | 67 | if (aa_ns_visible(curr, view, subns)) { |
cff281f6 JJ |
68 | /* at this point if a ns is visible it is in a view ns |
69 | * thus the curr ns.hname is a prefix of its name. | |
70 | * Only output the virtualized portion of the name | |
71 | * Add + 2 to skip over // separating curr hname prefix | |
72 | * from the visible tail of the views hname | |
73 | */ | |
74 | return view->base.hname + strlen(curr->base.hname) + 2; | |
75 | } | |
76 | ||
77 | return aa_hidden_ns_name; | |
78 | } | |
79 | ||
80 | /** | |
98849dff | 81 | * alloc_ns - allocate, initialize and return a new namespace |
cff281f6 JJ |
82 | * @prefix: parent namespace name (MAYBE NULL) |
83 | * @name: a preallocated name (NOT NULL) | |
84 | * | |
85 | * Returns: refcounted namespace or NULL on failure. | |
86 | */ | |
98849dff | 87 | static struct aa_ns *alloc_ns(const char *prefix, const char *name) |
cff281f6 | 88 | { |
98849dff | 89 | struct aa_ns *ns; |
cff281f6 JJ |
90 | |
91 | ns = kzalloc(sizeof(*ns), GFP_KERNEL); | |
92 | AA_DEBUG("%s(%p)\n", __func__, ns); | |
93 | if (!ns) | |
94 | return NULL; | |
d102d895 | 95 | if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL)) |
cff281f6 JJ |
96 | goto fail_ns; |
97 | ||
98 | INIT_LIST_HEAD(&ns->sub_ns); | |
5d5182ca | 99 | INIT_LIST_HEAD(&ns->rawdata_list); |
cff281f6 | 100 | mutex_init(&ns->lock); |
d9bf2c26 | 101 | init_waitqueue_head(&ns->wait); |
cff281f6 | 102 | |
98849dff | 103 | /* released by aa_free_ns() */ |
637f688d | 104 | ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL); |
cff281f6 JJ |
105 | if (!ns->unconfined) |
106 | goto fail_unconfined; | |
107 | ||
637f688d JJ |
108 | ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR | |
109 | FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; | |
cff281f6 | 110 | ns->unconfined->mode = APPARMOR_UNCONFINED; |
15372b97 JJ |
111 | ns->unconfined->file.dfa = aa_get_dfa(nulldfa); |
112 | ns->unconfined->policy.dfa = aa_get_dfa(nulldfa); | |
cff281f6 JJ |
113 | |
114 | /* ns and ns->unconfined share ns->unconfined refcount */ | |
115 | ns->unconfined->ns = ns; | |
116 | ||
117 | atomic_set(&ns->uniq_null, 0); | |
118 | ||
637f688d JJ |
119 | aa_labelset_init(&ns->labels); |
120 | ||
cff281f6 JJ |
121 | return ns; |
122 | ||
123 | fail_unconfined: | |
453431a5 | 124 | kfree_sensitive(ns->base.hname); |
cff281f6 | 125 | fail_ns: |
453431a5 | 126 | kfree_sensitive(ns); |
cff281f6 JJ |
127 | return NULL; |
128 | } | |
129 | ||
130 | /** | |
98849dff | 131 | * aa_free_ns - free a profile namespace |
cff281f6 JJ |
132 | * @ns: the namespace to free (MAYBE NULL) |
133 | * | |
134 | * Requires: All references to the namespace must have been put, if the | |
135 | * namespace was referenced by a profile confining a task, | |
136 | */ | |
98849dff | 137 | void aa_free_ns(struct aa_ns *ns) |
cff281f6 JJ |
138 | { |
139 | if (!ns) | |
140 | return; | |
141 | ||
142 | aa_policy_destroy(&ns->base); | |
637f688d | 143 | aa_labelset_destroy(&ns->labels); |
98849dff | 144 | aa_put_ns(ns->parent); |
cff281f6 JJ |
145 | |
146 | ns->unconfined->ns = NULL; | |
147 | aa_free_profile(ns->unconfined); | |
453431a5 | 148 | kfree_sensitive(ns); |
cff281f6 JJ |
149 | } |
150 | ||
151 | /** | |
9a2d40c1 | 152 | * aa_findn_ns - look up a profile namespace on the namespace list |
cff281f6 JJ |
153 | * @root: namespace to search in (NOT NULL) |
154 | * @name: name of namespace to find (NOT NULL) | |
9a2d40c1 | 155 | * @n: length of @name |
cff281f6 JJ |
156 | * |
157 | * Returns: a refcounted namespace on the list, or NULL if no namespace | |
158 | * called @name exists. | |
159 | * | |
160 | * refcount released by caller | |
161 | */ | |
9a2d40c1 | 162 | struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n) |
cff281f6 | 163 | { |
98849dff | 164 | struct aa_ns *ns = NULL; |
cff281f6 JJ |
165 | |
166 | rcu_read_lock(); | |
9a2d40c1 | 167 | ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n)); |
cff281f6 JJ |
168 | rcu_read_unlock(); |
169 | ||
170 | return ns; | |
171 | } | |
172 | ||
9a2d40c1 JJ |
173 | /** |
174 | * aa_find_ns - look up a profile namespace on the namespace list | |
175 | * @root: namespace to search in (NOT NULL) | |
176 | * @name: name of namespace to find (NOT NULL) | |
177 | * | |
178 | * Returns: a refcounted namespace on the list, or NULL if no namespace | |
179 | * called @name exists. | |
180 | * | |
181 | * refcount released by caller | |
182 | */ | |
183 | struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) | |
184 | { | |
185 | return aa_findn_ns(root, name, strlen(name)); | |
186 | } | |
187 | ||
3664268f JJ |
188 | /** |
189 | * __aa_lookupn_ns - lookup the namespace matching @hname | |
190 | * @base: base list to start looking up profile name from (NOT NULL) | |
191 | * @hname: hierarchical ns name (NOT NULL) | |
192 | * @n: length of @hname | |
193 | * | |
194 | * Requires: rcu_read_lock be held | |
195 | * | |
196 | * Returns: unrefcounted ns pointer or NULL if not found | |
197 | * | |
198 | * Do a relative name lookup, recursing through profile tree. | |
199 | */ | |
200 | struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n) | |
201 | { | |
202 | struct aa_ns *ns = view; | |
203 | const char *split; | |
204 | ||
205 | for (split = strnstr(hname, "//", n); split; | |
206 | split = strnstr(hname, "//", n)) { | |
207 | ns = __aa_findn_ns(&ns->sub_ns, hname, split - hname); | |
208 | if (!ns) | |
209 | return NULL; | |
210 | ||
211 | n -= split + 2 - hname; | |
212 | hname = split + 2; | |
213 | } | |
214 | ||
215 | if (n) | |
216 | return __aa_findn_ns(&ns->sub_ns, hname, n); | |
217 | return NULL; | |
218 | } | |
219 | ||
220 | /** | |
221 | * aa_lookupn_ns - look up a policy namespace relative to @view | |
222 | * @view: namespace to search in (NOT NULL) | |
223 | * @name: name of namespace to find (NOT NULL) | |
224 | * @n: length of @name | |
225 | * | |
226 | * Returns: a refcounted namespace on the list, or NULL if no namespace | |
227 | * called @name exists. | |
228 | * | |
229 | * refcount released by caller | |
230 | */ | |
231 | struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n) | |
232 | { | |
233 | struct aa_ns *ns = NULL; | |
234 | ||
235 | rcu_read_lock(); | |
236 | ns = aa_get_ns(__aa_lookupn_ns(view, name, n)); | |
237 | rcu_read_unlock(); | |
238 | ||
239 | return ns; | |
240 | } | |
241 | ||
73688d1e JJ |
242 | static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, |
243 | struct dentry *dir) | |
244 | { | |
245 | struct aa_ns *ns; | |
246 | int error; | |
247 | ||
248 | AA_BUG(!parent); | |
249 | AA_BUG(!name); | |
250 | AA_BUG(!mutex_is_locked(&parent->lock)); | |
251 | ||
252 | ns = alloc_ns(parent->base.hname, name); | |
253 | if (!ns) | |
0a6b2923 | 254 | return ERR_PTR(-ENOMEM); |
feb3c766 JJ |
255 | ns->level = parent->level + 1; |
256 | mutex_lock_nested(&ns->lock, ns->level); | |
98407f0a | 257 | error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name, dir); |
73688d1e JJ |
258 | if (error) { |
259 | AA_ERROR("Failed to create interface for ns %s\n", | |
260 | ns->base.name); | |
261 | mutex_unlock(&ns->lock); | |
262 | aa_free_ns(ns); | |
263 | return ERR_PTR(error); | |
264 | } | |
265 | ns->parent = aa_get_ns(parent); | |
266 | list_add_rcu(&ns->base.list, &parent->sub_ns); | |
267 | /* add list ref */ | |
268 | aa_get_ns(ns); | |
269 | mutex_unlock(&ns->lock); | |
270 | ||
271 | return ns; | |
272 | } | |
273 | ||
cff281f6 | 274 | /** |
73688d1e JJ |
275 | * aa_create_ns - create an ns, fail if it already exists |
276 | * @parent: the parent of the namespace being created | |
277 | * @name: the name of the namespace | |
278 | * @dir: if not null the dir to put the ns entries in | |
cff281f6 | 279 | * |
73688d1e | 280 | * Returns: the a refcounted ns that has been add or an ERR_PTR |
cff281f6 | 281 | */ |
73688d1e JJ |
282 | struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name, |
283 | struct dentry *dir) | |
cff281f6 | 284 | { |
73688d1e | 285 | struct aa_ns *ns; |
cff281f6 | 286 | |
73688d1e | 287 | AA_BUG(!mutex_is_locked(&parent->lock)); |
cff281f6 | 288 | |
73688d1e JJ |
289 | /* try and find the specified ns */ |
290 | /* released by caller */ | |
291 | ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); | |
292 | if (!ns) | |
293 | ns = __aa_create_ns(parent, name, dir); | |
294 | else | |
295 | ns = ERR_PTR(-EEXIST); | |
cff281f6 | 296 | |
73688d1e JJ |
297 | /* return ref */ |
298 | return ns; | |
299 | } | |
cff281f6 | 300 | |
73688d1e JJ |
301 | /** |
302 | * aa_prepare_ns - find an existing or create a new namespace of @name | |
303 | * @parent: ns to treat as parent | |
304 | * @name: the namespace to find or add (NOT NULL) | |
305 | * | |
306 | * Returns: refcounted namespace or PTR_ERR if failed to create one | |
307 | */ | |
308 | struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name) | |
309 | { | |
310 | struct aa_ns *ns; | |
311 | ||
feb3c766 | 312 | mutex_lock_nested(&parent->lock, parent->level); |
cff281f6 JJ |
313 | /* try and find the specified ns and if it doesn't exist create it */ |
314 | /* released by caller */ | |
73688d1e JJ |
315 | ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); |
316 | if (!ns) | |
317 | ns = __aa_create_ns(parent, name, NULL); | |
318 | mutex_unlock(&parent->lock); | |
cff281f6 JJ |
319 | |
320 | /* return ref */ | |
321 | return ns; | |
322 | } | |
323 | ||
324 | static void __ns_list_release(struct list_head *head); | |
325 | ||
326 | /** | |
98849dff | 327 | * destroy_ns - remove everything contained by @ns |
31617ddf | 328 | * @ns: namespace to have it contents removed (NOT NULL) |
cff281f6 | 329 | */ |
98849dff | 330 | static void destroy_ns(struct aa_ns *ns) |
cff281f6 JJ |
331 | { |
332 | if (!ns) | |
333 | return; | |
334 | ||
feb3c766 | 335 | mutex_lock_nested(&ns->lock, ns->level); |
cff281f6 JJ |
336 | /* release all profiles in this namespace */ |
337 | __aa_profile_list_release(&ns->base.profiles); | |
338 | ||
339 | /* release all sub namespaces */ | |
340 | __ns_list_release(&ns->sub_ns); | |
341 | ||
637f688d JJ |
342 | if (ns->parent) { |
343 | unsigned long flags; | |
344 | ||
345 | write_lock_irqsave(&ns->labels.lock, flags); | |
346 | __aa_proxy_redirect(ns_unconfined(ns), | |
347 | ns_unconfined(ns->parent)); | |
348 | write_unlock_irqrestore(&ns->labels.lock, flags); | |
349 | } | |
c97204ba | 350 | __aafs_ns_rmdir(ns); |
cff281f6 JJ |
351 | mutex_unlock(&ns->lock); |
352 | } | |
353 | ||
354 | /** | |
98849dff | 355 | * __aa_remove_ns - remove a namespace and all its children |
cff281f6 JJ |
356 | * @ns: namespace to be removed (NOT NULL) |
357 | * | |
358 | * Requires: ns->parent->lock be held and ns removed from parent. | |
359 | */ | |
98849dff | 360 | void __aa_remove_ns(struct aa_ns *ns) |
cff281f6 JJ |
361 | { |
362 | /* remove ns from namespace list */ | |
363 | list_del_rcu(&ns->base.list); | |
98849dff JJ |
364 | destroy_ns(ns); |
365 | aa_put_ns(ns); | |
cff281f6 JJ |
366 | } |
367 | ||
368 | /** | |
369 | * __ns_list_release - remove all profile namespaces on the list put refs | |
370 | * @head: list of profile namespaces (NOT NULL) | |
371 | * | |
372 | * Requires: namespace lock be held | |
373 | */ | |
374 | static void __ns_list_release(struct list_head *head) | |
375 | { | |
98849dff | 376 | struct aa_ns *ns, *tmp; |
cff281f6 JJ |
377 | |
378 | list_for_each_entry_safe(ns, tmp, head, base.list) | |
98849dff | 379 | __aa_remove_ns(ns); |
cff281f6 JJ |
380 | |
381 | } | |
382 | ||
383 | /** | |
31617ddf | 384 | * aa_alloc_root_ns - allocate the root profile namespace |
cff281f6 JJ |
385 | * |
386 | * Returns: %0 on success else error | |
387 | * | |
388 | */ | |
389 | int __init aa_alloc_root_ns(void) | |
390 | { | |
391 | /* released by aa_free_root_ns - used as list ref*/ | |
98849dff | 392 | root_ns = alloc_ns(NULL, "root"); |
cff281f6 JJ |
393 | if (!root_ns) |
394 | return -ENOMEM; | |
395 | ||
396 | return 0; | |
397 | } | |
398 | ||
399 | /** | |
400 | * aa_free_root_ns - free the root profile namespace | |
401 | */ | |
402 | void __init aa_free_root_ns(void) | |
403 | { | |
98849dff | 404 | struct aa_ns *ns = root_ns; |
cff281f6 JJ |
405 | |
406 | root_ns = NULL; | |
407 | ||
98849dff JJ |
408 | destroy_ns(ns); |
409 | aa_put_ns(ns); | |
cff281f6 | 410 | } |