diff options
author | John Johansen <john.johansen@canonical.com> | 2013-07-11 00:06:43 -0400 |
---|---|---|
committer | John Johansen <john.johansen@canonical.com> | 2013-08-14 14:42:06 -0400 |
commit | 01e2b670aa898a39259bc85c78e3d74820f4d3b6 (patch) | |
tree | cd78cea5f92788c213f2eb8ef287535ac1bb3327 /security | |
parent | dd51c84857630e77c139afe4d9bba65fc051dc3f (diff) |
apparmor: convert profile lists to RCU based locking
Signed-off-by: John Johansen <john.johansen@canonical.com>
Diffstat (limited to 'security')
-rw-r--r-- | security/apparmor/domain.c | 14 | ||||
-rw-r--r-- | security/apparmor/include/apparmor.h | 6 | ||||
-rw-r--r-- | security/apparmor/include/policy.h | 45 | ||||
-rw-r--r-- | security/apparmor/policy.c | 213 |
4 files changed, 167 insertions, 111 deletions
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 01b7bd669a88..454bcd7f3452 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c | |||
@@ -144,7 +144,7 @@ static struct aa_profile *__attach_match(const char *name, | |||
144 | int len = 0; | 144 | int len = 0; |
145 | struct aa_profile *profile, *candidate = NULL; | 145 | struct aa_profile *profile, *candidate = NULL; |
146 | 146 | ||
147 | list_for_each_entry(profile, head, base.list) { | 147 | list_for_each_entry_rcu(profile, head, base.list) { |
148 | if (profile->flags & PFLAG_NULL) | 148 | if (profile->flags & PFLAG_NULL) |
149 | continue; | 149 | continue; |
150 | if (profile->xmatch && profile->xmatch_len > len) { | 150 | if (profile->xmatch && profile->xmatch_len > len) { |
@@ -177,9 +177,9 @@ static struct aa_profile *find_attach(struct aa_namespace *ns, | |||
177 | { | 177 | { |
178 | struct aa_profile *profile; | 178 | struct aa_profile *profile; |
179 | 179 | ||
180 | read_lock(&ns->lock); | 180 | rcu_read_lock(); |
181 | profile = aa_get_profile(__attach_match(name, list)); | 181 | profile = aa_get_profile(__attach_match(name, list)); |
182 | read_unlock(&ns->lock); | 182 | rcu_read_unlock(); |
183 | 183 | ||
184 | return profile; | 184 | return profile; |
185 | } | 185 | } |
@@ -641,7 +641,10 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |||
641 | if (count) { | 641 | if (count) { |
642 | /* attempting to change into a new hat or switch to a sibling */ | 642 | /* attempting to change into a new hat or switch to a sibling */ |
643 | struct aa_profile *root; | 643 | struct aa_profile *root; |
644 | root = PROFILE_IS_HAT(profile) ? profile->parent : profile; | 644 | if (PROFILE_IS_HAT(profile)) |
645 | root = aa_get_profile_rcu(&profile->parent); | ||
646 | else | ||
647 | root = aa_get_profile(profile); | ||
645 | 648 | ||
646 | /* find first matching hat */ | 649 | /* find first matching hat */ |
647 | for (i = 0; i < count && !hat; i++) | 650 | for (i = 0; i < count && !hat; i++) |
@@ -653,6 +656,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |||
653 | error = -ECHILD; | 656 | error = -ECHILD; |
654 | else | 657 | else |
655 | error = -ENOENT; | 658 | error = -ENOENT; |
659 | aa_put_profile(root); | ||
656 | goto out; | 660 | goto out; |
657 | } | 661 | } |
658 | 662 | ||
@@ -667,6 +671,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |||
667 | 671 | ||
668 | /* freed below */ | 672 | /* freed below */ |
669 | name = new_compound_name(root->base.hname, hats[0]); | 673 | name = new_compound_name(root->base.hname, hats[0]); |
674 | aa_put_profile(root); | ||
670 | target = name; | 675 | target = name; |
671 | /* released below */ | 676 | /* released below */ |
672 | hat = aa_new_null_profile(profile, 1); | 677 | hat = aa_new_null_profile(profile, 1); |
@@ -676,6 +681,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |||
676 | goto audit; | 681 | goto audit; |
677 | } | 682 | } |
678 | } else { | 683 | } else { |
684 | aa_put_profile(root); | ||
679 | target = hat->base.hname; | 685 | target = hat->base.hname; |
680 | if (!PROFILE_IS_HAT(hat)) { | 686 | if (!PROFILE_IS_HAT(hat)) { |
681 | info = "target not hat"; | 687 | info = "target not hat"; |
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 1ba2ca56a6ef..8fb1488a3cd4 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h | |||
@@ -78,6 +78,12 @@ static inline void *kvzalloc(size_t size) | |||
78 | return __aa_kvmalloc(size, __GFP_ZERO); | 78 | return __aa_kvmalloc(size, __GFP_ZERO); |
79 | } | 79 | } |
80 | 80 | ||
81 | /* returns 0 if kref not incremented */ | ||
82 | static inline int kref_get_not0(struct kref *kref) | ||
83 | { | ||
84 | return atomic_inc_not_zero(&kref->refcount); | ||
85 | } | ||
86 | |||
81 | /** | 87 | /** |
82 | * aa_strneq - compare null terminated @str to a non null terminated substring | 88 | * aa_strneq - compare null terminated @str to a non null terminated substring |
83 | * @str: a null terminated string | 89 | * @str: a null terminated string |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index b25491a3046a..82487a853353 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h | |||
@@ -42,6 +42,8 @@ extern const char *const profile_mode_names[]; | |||
42 | 42 | ||
43 | #define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) | 43 | #define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) |
44 | 44 | ||
45 | #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) | ||
46 | |||
45 | /* | 47 | /* |
46 | * FIXME: currently need a clean way to replace and remove profiles as a | 48 | * FIXME: currently need a clean way to replace and remove profiles as a |
47 | * set. It should be done at the namespace level. | 49 | * set. It should be done at the namespace level. |
@@ -75,6 +77,7 @@ struct aa_profile; | |||
75 | * @hname - The hierarchical name | 77 | * @hname - The hierarchical name |
76 | * @count: reference count of the obj | 78 | * @count: reference count of the obj |
77 | * @list: list policy object is on | 79 | * @list: list policy object is on |
80 | * @rcu: rcu head used when removing from @list | ||
78 | * @profiles: head of the profiles list contained in the object | 81 | * @profiles: head of the profiles list contained in the object |
79 | */ | 82 | */ |
80 | struct aa_policy { | 83 | struct aa_policy { |
@@ -83,6 +86,7 @@ struct aa_policy { | |||
83 | struct kref count; | 86 | struct kref count; |
84 | struct list_head list; | 87 | struct list_head list; |
85 | struct list_head profiles; | 88 | struct list_head profiles; |
89 | struct rcu_head rcu; | ||
86 | }; | 90 | }; |
87 | 91 | ||
88 | /* struct aa_ns_acct - accounting of profiles in namespace | 92 | /* struct aa_ns_acct - accounting of profiles in namespace |
@@ -124,7 +128,7 @@ struct aa_ns_acct { | |||
124 | struct aa_namespace { | 128 | struct aa_namespace { |
125 | struct aa_policy base; | 129 | struct aa_policy base; |
126 | struct aa_namespace *parent; | 130 | struct aa_namespace *parent; |
127 | rwlock_t lock; | 131 | struct mutex lock; |
128 | struct aa_ns_acct acct; | 132 | struct aa_ns_acct acct; |
129 | struct aa_profile *unconfined; | 133 | struct aa_profile *unconfined; |
130 | struct list_head sub_ns; | 134 | struct list_head sub_ns; |
@@ -166,7 +170,7 @@ struct aa_policydb { | |||
166 | * attachments are determined by profile X transition rules. | 170 | * attachments are determined by profile X transition rules. |
167 | * | 171 | * |
168 | * The @replacedby field is write protected by the profile lock. Reads | 172 | * The @replacedby field is write protected by the profile lock. Reads |
169 | * are assumed to be atomic, and are done without locking. | 173 | * are assumed to be atomic. |
170 | * | 174 | * |
171 | * Profiles have a hierarchy where hats and children profiles keep | 175 | * Profiles have a hierarchy where hats and children profiles keep |
172 | * a reference to their parent. | 176 | * a reference to their parent. |
@@ -177,7 +181,7 @@ struct aa_policydb { | |||
177 | */ | 181 | */ |
178 | struct aa_profile { | 182 | struct aa_profile { |
179 | struct aa_policy base; | 183 | struct aa_policy base; |
180 | struct aa_profile *parent; | 184 | struct aa_profile __rcu *parent; |
181 | 185 | ||
182 | struct aa_namespace *ns; | 186 | struct aa_namespace *ns; |
183 | struct aa_profile *replacedby; | 187 | struct aa_profile *replacedby; |
@@ -296,6 +300,41 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p) | |||
296 | } | 300 | } |
297 | 301 | ||
298 | /** | 302 | /** |
303 | * aa_get_profile_not0 - increment refcount on profile @p found via lookup | ||
304 | * @p: profile (MAYBE NULL) | ||
305 | * | ||
306 | * Returns: pointer to @p if @p is NULL will return NULL | ||
307 | * Requires: @p must be held with valid refcount when called | ||
308 | */ | ||
309 | static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) | ||
310 | { | ||
311 | if (p && kref_get_not0(&p->base.count)) | ||
312 | return p; | ||
313 | |||
314 | return NULL; | ||
315 | } | ||
316 | |||
317 | /** | ||
318 | * aa_get_profile_rcu - increment a refcount profile that can be replaced | ||
319 | * @p: pointer to profile that can be replaced (NOT NULL) | ||
320 | * | ||
321 | * Returns: pointer to a refcounted profile. | ||
322 | * else NULL if no profile | ||
323 | */ | ||
324 | static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) | ||
325 | { | ||
326 | struct aa_profile *c; | ||
327 | |||
328 | rcu_read_lock(); | ||
329 | do { | ||
330 | c = rcu_dereference(*p); | ||
331 | } while (c && !kref_get_not0(&c->base.count)); | ||
332 | rcu_read_unlock(); | ||
333 | |||
334 | return c; | ||
335 | } | ||
336 | |||
337 | /** | ||
299 | * aa_put_profile - decrement refcount on profile @p | 338 | * aa_put_profile - decrement refcount on profile @p |
300 | * @p: profile (MAYBE NULL) | 339 | * @p: profile (MAYBE NULL) |
301 | */ | 340 | */ |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 407b442c0a2c..25bbbb482bb6 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c | |||
@@ -153,13 +153,13 @@ static bool policy_init(struct aa_policy *policy, const char *prefix, | |||
153 | static void policy_destroy(struct aa_policy *policy) | 153 | static void policy_destroy(struct aa_policy *policy) |
154 | { | 154 | { |
155 | /* still contains profiles -- invalid */ | 155 | /* still contains profiles -- invalid */ |
156 | if (!list_empty(&policy->profiles)) { | 156 | if (on_list_rcu(&policy->profiles)) { |
157 | AA_ERROR("%s: internal error, " | 157 | AA_ERROR("%s: internal error, " |
158 | "policy '%s' still contains profiles\n", | 158 | "policy '%s' still contains profiles\n", |
159 | __func__, policy->name); | 159 | __func__, policy->name); |
160 | BUG(); | 160 | BUG(); |
161 | } | 161 | } |
162 | if (!list_empty(&policy->list)) { | 162 | if (on_list_rcu(&policy->list)) { |
163 | AA_ERROR("%s: internal error, policy '%s' still on list\n", | 163 | AA_ERROR("%s: internal error, policy '%s' still on list\n", |
164 | __func__, policy->name); | 164 | __func__, policy->name); |
165 | BUG(); | 165 | BUG(); |
@@ -174,7 +174,7 @@ static void policy_destroy(struct aa_policy *policy) | |||
174 | * @head: list to search (NOT NULL) | 174 | * @head: list to search (NOT NULL) |
175 | * @name: name to search for (NOT NULL) | 175 | * @name: name to search for (NOT NULL) |
176 | * | 176 | * |
177 | * Requires: correct locks for the @head list be held | 177 | * Requires: rcu_read_lock be held |
178 | * | 178 | * |
179 | * Returns: unrefcounted policy that match @name or NULL if not found | 179 | * Returns: unrefcounted policy that match @name or NULL if not found |
180 | */ | 180 | */ |
@@ -182,7 +182,7 @@ static struct aa_policy *__policy_find(struct list_head *head, const char *name) | |||
182 | { | 182 | { |
183 | struct aa_policy *policy; | 183 | struct aa_policy *policy; |
184 | 184 | ||
185 | list_for_each_entry(policy, head, list) { | 185 | list_for_each_entry_rcu(policy, head, list) { |
186 | if (!strcmp(policy->name, name)) | 186 | if (!strcmp(policy->name, name)) |
187 | return policy; | 187 | return policy; |
188 | } | 188 | } |
@@ -195,7 +195,7 @@ static struct aa_policy *__policy_find(struct list_head *head, const char *name) | |||
195 | * @str: string to search for (NOT NULL) | 195 | * @str: string to search for (NOT NULL) |
196 | * @len: length of match required | 196 | * @len: length of match required |
197 | * | 197 | * |
198 | * Requires: correct locks for the @head list be held | 198 | * Requires: rcu_read_lock be held |
199 | * | 199 | * |
200 | * Returns: unrefcounted policy that match @str or NULL if not found | 200 | * Returns: unrefcounted policy that match @str or NULL if not found |
201 | * | 201 | * |
@@ -207,7 +207,7 @@ static struct aa_policy *__policy_strn_find(struct list_head *head, | |||
207 | { | 207 | { |
208 | struct aa_policy *policy; | 208 | struct aa_policy *policy; |
209 | 209 | ||
210 | list_for_each_entry(policy, head, list) { | 210 | list_for_each_entry_rcu(policy, head, list) { |
211 | if (aa_strneq(policy->name, str, len)) | 211 | if (aa_strneq(policy->name, str, len)) |
212 | return policy; | 212 | return policy; |
213 | } | 213 | } |
@@ -284,7 +284,7 @@ static struct aa_namespace *alloc_namespace(const char *prefix, | |||
284 | goto fail_ns; | 284 | goto fail_ns; |
285 | 285 | ||
286 | INIT_LIST_HEAD(&ns->sub_ns); | 286 | INIT_LIST_HEAD(&ns->sub_ns); |
287 | rwlock_init(&ns->lock); | 287 | mutex_init(&ns->lock); |
288 | 288 | ||
289 | /* released by free_namespace */ | 289 | /* released by free_namespace */ |
290 | ns->unconfined = aa_alloc_profile("unconfined"); | 290 | ns->unconfined = aa_alloc_profile("unconfined"); |
@@ -350,7 +350,7 @@ void aa_free_namespace_kref(struct kref *kref) | |||
350 | * | 350 | * |
351 | * Returns: unrefcounted namespace | 351 | * Returns: unrefcounted namespace |
352 | * | 352 | * |
353 | * Requires: ns lock be held | 353 | * Requires: rcu_read_lock be held |
354 | */ | 354 | */ |
355 | static struct aa_namespace *__aa_find_namespace(struct list_head *head, | 355 | static struct aa_namespace *__aa_find_namespace(struct list_head *head, |
356 | const char *name) | 356 | const char *name) |
@@ -373,9 +373,9 @@ struct aa_namespace *aa_find_namespace(struct aa_namespace *root, | |||
373 | { | 373 | { |
374 | struct aa_namespace *ns = NULL; | 374 | struct aa_namespace *ns = NULL; |
375 | 375 | ||
376 | read_lock(&root->lock); | 376 | rcu_read_lock(); |
377 | ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); | 377 | ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); |
378 | read_unlock(&root->lock); | 378 | rcu_read_unlock(); |
379 | 379 | ||
380 | return ns; | 380 | return ns; |
381 | } | 381 | } |
@@ -392,7 +392,7 @@ static struct aa_namespace *aa_prepare_namespace(const char *name) | |||
392 | 392 | ||
393 | root = aa_current_profile()->ns; | 393 | root = aa_current_profile()->ns; |
394 | 394 | ||
395 | write_lock(&root->lock); | 395 | mutex_lock(&root->lock); |
396 | 396 | ||
397 | /* if name isn't specified the profile is loaded to the current ns */ | 397 | /* if name isn't specified the profile is loaded to the current ns */ |
398 | if (!name) { | 398 | if (!name) { |
@@ -405,31 +405,17 @@ static struct aa_namespace *aa_prepare_namespace(const char *name) | |||
405 | /* released by caller */ | 405 | /* released by caller */ |
406 | ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); | 406 | ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); |
407 | if (!ns) { | 407 | if (!ns) { |
408 | /* namespace not found */ | 408 | ns = alloc_namespace(root->base.hname, name); |
409 | struct aa_namespace *new_ns; | 409 | if (!ns) |
410 | write_unlock(&root->lock); | 410 | goto out; |
411 | new_ns = alloc_namespace(root->base.hname, name); | 411 | /* add parent ref */ |
412 | if (!new_ns) | 412 | ns->parent = aa_get_namespace(root); |
413 | return NULL; | 413 | list_add_rcu(&ns->base.list, &root->sub_ns); |
414 | write_lock(&root->lock); | 414 | /* add list ref */ |
415 | /* test for race when new_ns was allocated */ | 415 | aa_get_namespace(ns); |
416 | ns = __aa_find_namespace(&root->sub_ns, name); | ||
417 | if (!ns) { | ||
418 | /* add parent ref */ | ||
419 | new_ns->parent = aa_get_namespace(root); | ||
420 | |||
421 | list_add(&new_ns->base.list, &root->sub_ns); | ||
422 | /* add list ref */ | ||
423 | ns = aa_get_namespace(new_ns); | ||
424 | } else { | ||
425 | /* raced so free the new one */ | ||
426 | free_namespace(new_ns); | ||
427 | /* get reference on namespace */ | ||
428 | aa_get_namespace(ns); | ||
429 | } | ||
430 | } | 416 | } |
431 | out: | 417 | out: |
432 | write_unlock(&root->lock); | 418 | mutex_unlock(&root->lock); |
433 | 419 | ||
434 | /* return ref */ | 420 | /* return ref */ |
435 | return ns; | 421 | return ns; |
@@ -447,7 +433,7 @@ out: | |||
447 | static void __list_add_profile(struct list_head *list, | 433 | static void __list_add_profile(struct list_head *list, |
448 | struct aa_profile *profile) | 434 | struct aa_profile *profile) |
449 | { | 435 | { |
450 | list_add(&profile->base.list, list); | 436 | list_add_rcu(&profile->base.list, list); |
451 | /* get list reference */ | 437 | /* get list reference */ |
452 | aa_get_profile(profile); | 438 | aa_get_profile(profile); |
453 | } | 439 | } |
@@ -466,10 +452,8 @@ static void __list_add_profile(struct list_head *list, | |||
466 | */ | 452 | */ |
467 | static void __list_remove_profile(struct aa_profile *profile) | 453 | static void __list_remove_profile(struct aa_profile *profile) |
468 | { | 454 | { |
469 | list_del_init(&profile->base.list); | 455 | list_del_rcu(&profile->base.list); |
470 | if (!(profile->flags & PFLAG_NO_LIST_REF)) | 456 | aa_put_profile(profile); |
471 | /* release list reference */ | ||
472 | aa_put_profile(profile); | ||
473 | } | 457 | } |
474 | 458 | ||
475 | static void __profile_list_release(struct list_head *head); | 459 | static void __profile_list_release(struct list_head *head); |
@@ -510,17 +494,40 @@ static void __ns_list_release(struct list_head *head); | |||
510 | */ | 494 | */ |
511 | static void destroy_namespace(struct aa_namespace *ns) | 495 | static void destroy_namespace(struct aa_namespace *ns) |
512 | { | 496 | { |
497 | struct aa_profile *unconfined; | ||
498 | |||
513 | if (!ns) | 499 | if (!ns) |
514 | return; | 500 | return; |
515 | 501 | ||
516 | write_lock(&ns->lock); | 502 | mutex_lock(&ns->lock); |
517 | /* release all profiles in this namespace */ | 503 | /* release all profiles in this namespace */ |
518 | __profile_list_release(&ns->base.profiles); | 504 | __profile_list_release(&ns->base.profiles); |
519 | 505 | ||
520 | /* release all sub namespaces */ | 506 | /* release all sub namespaces */ |
521 | __ns_list_release(&ns->sub_ns); | 507 | __ns_list_release(&ns->sub_ns); |
522 | 508 | ||
523 | write_unlock(&ns->lock); | 509 | unconfined = ns->unconfined; |
510 | /* | ||
511 | * break the ns, unconfined profile cyclic reference and forward | ||
512 | * all new unconfined profiles requests to the parent namespace | ||
513 | * This will result in all confined tasks that have a profile | ||
514 | * being removed, inheriting the parent->unconfined profile. | ||
515 | */ | ||
516 | if (ns->parent) | ||
517 | ns->unconfined = aa_get_profile(ns->parent->unconfined); | ||
518 | |||
519 | /* release original ns->unconfined ref */ | ||
520 | aa_put_profile(unconfined); | ||
521 | |||
522 | mutex_unlock(&ns->lock); | ||
523 | } | ||
524 | |||
525 | static void aa_put_ns_rcu(struct rcu_head *head) | ||
526 | { | ||
527 | struct aa_namespace *ns = container_of(head, struct aa_namespace, | ||
528 | base.rcu); | ||
529 | /* release ns->base.list ref */ | ||
530 | aa_put_namespace(ns); | ||
524 | } | 531 | } |
525 | 532 | ||
526 | /** | 533 | /** |
@@ -531,26 +538,12 @@ static void destroy_namespace(struct aa_namespace *ns) | |||
531 | */ | 538 | */ |
532 | static void __remove_namespace(struct aa_namespace *ns) | 539 | static void __remove_namespace(struct aa_namespace *ns) |
533 | { | 540 | { |
534 | struct aa_profile *unconfined = ns->unconfined; | ||
535 | |||
536 | /* remove ns from namespace list */ | 541 | /* remove ns from namespace list */ |
537 | list_del_init(&ns->base.list); | 542 | list_del_rcu(&ns->base.list); |
538 | |||
539 | /* | ||
540 | * break the ns, unconfined profile cyclic reference and forward | ||
541 | * all new unconfined profiles requests to the parent namespace | ||
542 | * This will result in all confined tasks that have a profile | ||
543 | * being removed, inheriting the parent->unconfined profile. | ||
544 | */ | ||
545 | if (ns->parent) | ||
546 | ns->unconfined = aa_get_profile(ns->parent->unconfined); | ||
547 | 543 | ||
548 | destroy_namespace(ns); | 544 | destroy_namespace(ns); |
549 | 545 | ||
550 | /* release original ns->unconfined ref */ | 546 | call_rcu(&ns->base.rcu, aa_put_ns_rcu); |
551 | aa_put_profile(unconfined); | ||
552 | /* release ns->base.list ref, from removal above */ | ||
553 | aa_put_namespace(ns); | ||
554 | } | 547 | } |
555 | 548 | ||
556 | /** | 549 | /** |
@@ -614,16 +607,9 @@ static void free_profile(struct aa_profile *profile) | |||
614 | if (!profile) | 607 | if (!profile) |
615 | return; | 608 | return; |
616 | 609 | ||
617 | if (!list_empty(&profile->base.list)) { | ||
618 | AA_ERROR("%s: internal error, " | ||
619 | "profile '%s' still on ns list\n", | ||
620 | __func__, profile->base.name); | ||
621 | BUG(); | ||
622 | } | ||
623 | |||
624 | /* free children profiles */ | 610 | /* free children profiles */ |
625 | policy_destroy(&profile->base); | 611 | policy_destroy(&profile->base); |
626 | aa_put_profile(profile->parent); | 612 | aa_put_profile(rcu_access_pointer(profile->parent)); |
627 | 613 | ||
628 | aa_put_namespace(profile->ns); | 614 | aa_put_namespace(profile->ns); |
629 | kzfree(profile->rename); | 615 | kzfree(profile->rename); |
@@ -661,6 +647,16 @@ static void free_profile(struct aa_profile *profile) | |||
661 | } | 647 | } |
662 | 648 | ||
663 | /** | 649 | /** |
650 | * aa_free_profile_rcu - free aa_profile by rcu (called by aa_free_profile_kref) | ||
651 | * @head: rcu_head callback for freeing of a profile (NOT NULL) | ||
652 | */ | ||
653 | static void aa_free_profile_rcu(struct rcu_head *head) | ||
654 | { | ||
655 | struct aa_profile *p = container_of(head, struct aa_profile, base.rcu); | ||
656 | free_profile(p); | ||
657 | } | ||
658 | |||
659 | /** | ||
664 | * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) | 660 | * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) |
665 | * @kr: kref callback for freeing of a profile (NOT NULL) | 661 | * @kr: kref callback for freeing of a profile (NOT NULL) |
666 | */ | 662 | */ |
@@ -668,8 +664,7 @@ void aa_free_profile_kref(struct kref *kref) | |||
668 | { | 664 | { |
669 | struct aa_profile *p = container_of(kref, struct aa_profile, | 665 | struct aa_profile *p = container_of(kref, struct aa_profile, |
670 | base.count); | 666 | base.count); |
671 | 667 | call_rcu(&p->base.rcu, aa_free_profile_rcu); | |
672 | free_profile(p); | ||
673 | } | 668 | } |
674 | 669 | ||
675 | /** | 670 | /** |
@@ -733,12 +728,12 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) | |||
733 | profile->flags |= PFLAG_HAT; | 728 | profile->flags |= PFLAG_HAT; |
734 | 729 | ||
735 | /* released on free_profile */ | 730 | /* released on free_profile */ |
736 | profile->parent = aa_get_profile(parent); | 731 | rcu_assign_pointer(profile->parent, aa_get_profile(parent)); |
737 | profile->ns = aa_get_namespace(parent->ns); | 732 | profile->ns = aa_get_namespace(parent->ns); |
738 | 733 | ||
739 | write_lock(&profile->ns->lock); | 734 | mutex_lock(&profile->ns->lock); |
740 | __list_add_profile(&parent->base.profiles, profile); | 735 | __list_add_profile(&parent->base.profiles, profile); |
741 | write_unlock(&profile->ns->lock); | 736 | mutex_unlock(&profile->ns->lock); |
742 | 737 | ||
743 | /* refcount released by caller */ | 738 | /* refcount released by caller */ |
744 | return profile; | 739 | return profile; |
@@ -754,7 +749,7 @@ fail: | |||
754 | * @head: list to search (NOT NULL) | 749 | * @head: list to search (NOT NULL) |
755 | * @name: name of profile (NOT NULL) | 750 | * @name: name of profile (NOT NULL) |
756 | * | 751 | * |
757 | * Requires: ns lock protecting list be held | 752 | * Requires: rcu_read_lock be held |
758 | * | 753 | * |
759 | * Returns: unrefcounted profile ptr, or NULL if not found | 754 | * Returns: unrefcounted profile ptr, or NULL if not found |
760 | */ | 755 | */ |
@@ -769,7 +764,7 @@ static struct aa_profile *__find_child(struct list_head *head, const char *name) | |||
769 | * @name: name of profile (NOT NULL) | 764 | * @name: name of profile (NOT NULL) |
770 | * @len: length of @name substring to match | 765 | * @len: length of @name substring to match |
771 | * | 766 | * |
772 | * Requires: ns lock protecting list be held | 767 | * Requires: rcu_read_lock be held |
773 | * | 768 | * |
774 | * Returns: unrefcounted profile ptr, or NULL if not found | 769 | * Returns: unrefcounted profile ptr, or NULL if not found |
775 | */ | 770 | */ |
@@ -790,9 +785,9 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) | |||
790 | { | 785 | { |
791 | struct aa_profile *profile; | 786 | struct aa_profile *profile; |
792 | 787 | ||
793 | read_lock(&parent->ns->lock); | 788 | rcu_read_lock(); |
794 | profile = aa_get_profile(__find_child(&parent->base.profiles, name)); | 789 | profile = aa_get_profile(__find_child(&parent->base.profiles, name)); |
795 | read_unlock(&parent->ns->lock); | 790 | rcu_read_unlock(); |
796 | 791 | ||
797 | /* refcount released by caller */ | 792 | /* refcount released by caller */ |
798 | return profile; | 793 | return profile; |
@@ -807,7 +802,7 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) | |||
807 | * that matches hname does not need to exist, in general this | 802 | * that matches hname does not need to exist, in general this |
808 | * is used to load a new profile. | 803 | * is used to load a new profile. |
809 | * | 804 | * |
810 | * Requires: ns->lock be held | 805 | * Requires: rcu_read_lock be held |
811 | * | 806 | * |
812 | * Returns: unrefcounted policy or NULL if not found | 807 | * Returns: unrefcounted policy or NULL if not found |
813 | */ | 808 | */ |
@@ -839,7 +834,7 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns, | |||
839 | * @base: base list to start looking up profile name from (NOT NULL) | 834 | * @base: base list to start looking up profile name from (NOT NULL) |
840 | * @hname: hierarchical profile name (NOT NULL) | 835 | * @hname: hierarchical profile name (NOT NULL) |
841 | * | 836 | * |
842 | * Requires: ns->lock be held | 837 | * Requires: rcu_read_lock be held |
843 | * | 838 | * |
844 | * Returns: unrefcounted profile pointer or NULL if not found | 839 | * Returns: unrefcounted profile pointer or NULL if not found |
845 | * | 840 | * |
@@ -878,9 +873,11 @@ struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) | |||
878 | { | 873 | { |
879 | struct aa_profile *profile; | 874 | struct aa_profile *profile; |
880 | 875 | ||
881 | read_lock(&ns->lock); | 876 | rcu_read_lock(); |
882 | profile = aa_get_profile(__lookup_profile(&ns->base, hname)); | 877 | do { |
883 | read_unlock(&ns->lock); | 878 | profile = __lookup_profile(&ns->base, hname); |
879 | } while (profile && !aa_get_profile_not0(profile)); | ||
880 | rcu_read_unlock(); | ||
884 | 881 | ||
885 | /* the unconfined profile is not in the regular profile list */ | 882 | /* the unconfined profile is not in the regular profile list */ |
886 | if (!profile && strcmp(hname, "unconfined") == 0) | 883 | if (!profile && strcmp(hname, "unconfined") == 0) |
@@ -1002,7 +999,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new) | |||
1002 | 999 | ||
1003 | if (!list_empty(&old->base.profiles)) { | 1000 | if (!list_empty(&old->base.profiles)) { |
1004 | LIST_HEAD(lh); | 1001 | LIST_HEAD(lh); |
1005 | list_splice_init(&old->base.profiles, &lh); | 1002 | list_splice_init_rcu(&old->base.profiles, &lh, synchronize_rcu); |
1006 | 1003 | ||
1007 | list_for_each_entry_safe(child, tmp, &lh, base.list) { | 1004 | list_for_each_entry_safe(child, tmp, &lh, base.list) { |
1008 | struct aa_profile *p; | 1005 | struct aa_profile *p; |
@@ -1018,20 +1015,24 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new) | |||
1018 | /* inherit @child and its children */ | 1015 | /* inherit @child and its children */ |
1019 | /* TODO: update hname of inherited children */ | 1016 | /* TODO: update hname of inherited children */ |
1020 | /* list refcount transferred to @new */ | 1017 | /* list refcount transferred to @new */ |
1021 | list_add(&child->base.list, &new->base.profiles); | 1018 | p = rcu_dereference_protected(child->parent, |
1022 | aa_put_profile(child->parent); | 1019 | mutex_is_locked(&child->ns->lock)); |
1023 | child->parent = aa_get_profile(new); | 1020 | rcu_assign_pointer(child->parent, aa_get_profile(new)); |
1021 | list_add_rcu(&child->base.list, &new->base.profiles); | ||
1022 | aa_put_profile(p); | ||
1024 | } | 1023 | } |
1025 | } | 1024 | } |
1026 | 1025 | ||
1027 | if (!new->parent) | 1026 | if (!rcu_access_pointer(new->parent)) { |
1028 | new->parent = aa_get_profile(old->parent); | 1027 | struct aa_profile *parent = rcu_dereference(old->parent); |
1028 | rcu_assign_pointer(new->parent, aa_get_profile(parent)); | ||
1029 | } | ||
1029 | /* released by free_profile */ | 1030 | /* released by free_profile */ |
1030 | old->replacedby = aa_get_profile(new); | 1031 | old->replacedby = aa_get_profile(new); |
1031 | 1032 | ||
1032 | if (list_empty(&new->base.list)) { | 1033 | if (list_empty(&new->base.list)) { |
1033 | /* new is not on a list already */ | 1034 | /* new is not on a list already */ |
1034 | list_replace_init(&old->base.list, &new->base.list); | 1035 | list_replace_rcu(&old->base.list, &new->base.list); |
1035 | aa_get_profile(new); | 1036 | aa_get_profile(new); |
1036 | aa_put_profile(old); | 1037 | aa_put_profile(old); |
1037 | } else | 1038 | } else |
@@ -1099,7 +1100,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |||
1099 | goto fail; | 1100 | goto fail; |
1100 | } | 1101 | } |
1101 | 1102 | ||
1102 | write_lock(&ns->lock); | 1103 | mutex_lock(&ns->lock); |
1103 | /* setup parent and ns info */ | 1104 | /* setup parent and ns info */ |
1104 | list_for_each_entry(ent, &lh, list) { | 1105 | list_for_each_entry(ent, &lh, list) { |
1105 | struct aa_policy *policy; | 1106 | struct aa_policy *policy; |
@@ -1135,11 +1136,12 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |||
1135 | name = ent->new->base.hname; | 1136 | name = ent->new->base.hname; |
1136 | goto fail_lock; | 1137 | goto fail_lock; |
1137 | } | 1138 | } |
1138 | ent->new->parent = aa_get_profile(p); | 1139 | rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); |
1139 | } else if (policy != &ns->base) | 1140 | } else if (policy != &ns->base) { |
1140 | /* released on profile replacement or free_profile */ | 1141 | /* released on profile replacement or free_profile */ |
1141 | ent->new->parent = aa_get_profile((struct aa_profile *) | 1142 | struct aa_profile *p = (struct aa_profile *) policy; |
1142 | policy); | 1143 | rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); |
1144 | } | ||
1143 | } | 1145 | } |
1144 | 1146 | ||
1145 | /* do actual replacement */ | 1147 | /* do actual replacement */ |
@@ -1156,13 +1158,16 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |||
1156 | } else if (ent->rename) { | 1158 | } else if (ent->rename) { |
1157 | __replace_profile(ent->rename, ent->new); | 1159 | __replace_profile(ent->rename, ent->new); |
1158 | } else if (ent->new->parent) { | 1160 | } else if (ent->new->parent) { |
1159 | struct aa_profile *parent; | 1161 | struct aa_profile *parent, *newest; |
1160 | parent = aa_newest_version(ent->new->parent); | 1162 | parent = rcu_dereference_protected(ent->new->parent, |
1163 | mutex_is_locked(&ns->lock)); | ||
1164 | newest = aa_newest_version(parent); | ||
1165 | |||
1161 | /* parent replaced in this atomic set? */ | 1166 | /* parent replaced in this atomic set? */ |
1162 | if (parent != ent->new->parent) { | 1167 | if (newest != parent) { |
1163 | aa_get_profile(parent); | 1168 | aa_get_profile(newest); |
1164 | aa_put_profile(ent->new->parent); | 1169 | aa_put_profile(parent); |
1165 | ent->new->parent = parent; | 1170 | rcu_assign_pointer(ent->new->parent, newest); |
1166 | } | 1171 | } |
1167 | __list_add_profile(&parent->base.profiles, ent->new); | 1172 | __list_add_profile(&parent->base.profiles, ent->new); |
1168 | } else | 1173 | } else |
@@ -1170,7 +1175,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |||
1170 | 1175 | ||
1171 | aa_load_ent_free(ent); | 1176 | aa_load_ent_free(ent); |
1172 | } | 1177 | } |
1173 | write_unlock(&ns->lock); | 1178 | mutex_unlock(&ns->lock); |
1174 | 1179 | ||
1175 | out: | 1180 | out: |
1176 | aa_put_namespace(ns); | 1181 | aa_put_namespace(ns); |
@@ -1180,7 +1185,7 @@ out: | |||
1180 | return size; | 1185 | return size; |
1181 | 1186 | ||
1182 | fail_lock: | 1187 | fail_lock: |
1183 | write_unlock(&ns->lock); | 1188 | mutex_unlock(&ns->lock); |
1184 | fail: | 1189 | fail: |
1185 | error = audit_policy(op, GFP_KERNEL, name, info, error); | 1190 | error = audit_policy(op, GFP_KERNEL, name, info, error); |
1186 | 1191 | ||
@@ -1235,12 +1240,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) | |||
1235 | 1240 | ||
1236 | if (!name) { | 1241 | if (!name) { |
1237 | /* remove namespace - can only happen if fqname[0] == ':' */ | 1242 | /* remove namespace - can only happen if fqname[0] == ':' */ |
1238 | write_lock(&ns->parent->lock); | 1243 | mutex_lock(&ns->parent->lock); |
1239 | __remove_namespace(ns); | 1244 | __remove_namespace(ns); |
1240 | write_unlock(&ns->parent->lock); | 1245 | mutex_unlock(&ns->parent->lock); |
1241 | } else { | 1246 | } else { |
1242 | /* remove profile */ | 1247 | /* remove profile */ |
1243 | write_lock(&ns->lock); | 1248 | mutex_lock(&ns->lock); |
1244 | profile = aa_get_profile(__lookup_profile(&ns->base, name)); | 1249 | profile = aa_get_profile(__lookup_profile(&ns->base, name)); |
1245 | if (!profile) { | 1250 | if (!profile) { |
1246 | error = -ENOENT; | 1251 | error = -ENOENT; |
@@ -1249,7 +1254,7 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) | |||
1249 | } | 1254 | } |
1250 | name = profile->base.hname; | 1255 | name = profile->base.hname; |
1251 | __remove_profile(profile); | 1256 | __remove_profile(profile); |
1252 | write_unlock(&ns->lock); | 1257 | mutex_unlock(&ns->lock); |
1253 | } | 1258 | } |
1254 | 1259 | ||
1255 | /* don't fail removal if audit fails */ | 1260 | /* don't fail removal if audit fails */ |
@@ -1259,7 +1264,7 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) | |||
1259 | return size; | 1264 | return size; |
1260 | 1265 | ||
1261 | fail_ns_lock: | 1266 | fail_ns_lock: |
1262 | write_unlock(&ns->lock); | 1267 | mutex_unlock(&ns->lock); |
1263 | aa_put_namespace(ns); | 1268 | aa_put_namespace(ns); |
1264 | 1269 | ||
1265 | fail: | 1270 | fail: |