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