diff options
author | Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> | 2011-06-26 10:20:55 -0400 |
---|---|---|
committer | James Morris <jmorris@namei.org> | 2011-06-28 19:31:21 -0400 |
commit | 2e503bbb435ae418aebbe4aeede1c6f2a33d6f74 (patch) | |
tree | c6b783c245716cf87b337b2a855e742133afb7ac /security/tomoyo/gc.c | |
parent | 5625f2e3266319fd29fe4f1c76ccd3f550c79ac4 (diff) |
TOMOYO: Fix lockdep warning.
Currently TOMOYO holds SRCU lock upon open() and releases it upon close()
because list elements stored in the "struct tomoyo_io_buffer" instances are
accessed until close() is called. However, such SRCU usage causes lockdep to
complain about leaving the kernel with SRCU lock held.
This patch solves the warning by holding/releasing SRCU upon each
read()/write(). This patch is doing something similar to calling kfree()
without calling synchronize_srcu(), by selectively deferring kfree() by keeping
track of the "struct tomoyo_io_buffer" instances.
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'security/tomoyo/gc.c')
-rw-r--r-- | security/tomoyo/gc.c | 278 |
1 files changed, 257 insertions, 21 deletions
diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index 782e844dca7..1e1a6c8c832 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c | |||
@@ -11,13 +11,123 @@ | |||
11 | #include <linux/kthread.h> | 11 | #include <linux/kthread.h> |
12 | #include <linux/slab.h> | 12 | #include <linux/slab.h> |
13 | 13 | ||
14 | /* The list for "struct tomoyo_io_buffer". */ | ||
15 | static LIST_HEAD(tomoyo_io_buffer_list); | ||
16 | /* Lock for protecting tomoyo_io_buffer_list. */ | ||
17 | static DEFINE_SPINLOCK(tomoyo_io_buffer_list_lock); | ||
18 | |||
19 | /* Size of an element. */ | ||
20 | static const u8 tomoyo_element_size[TOMOYO_MAX_POLICY] = { | ||
21 | [TOMOYO_ID_GROUP] = sizeof(struct tomoyo_group), | ||
22 | [TOMOYO_ID_PATH_GROUP] = sizeof(struct tomoyo_path_group), | ||
23 | [TOMOYO_ID_NUMBER_GROUP] = sizeof(struct tomoyo_number_group), | ||
24 | [TOMOYO_ID_AGGREGATOR] = sizeof(struct tomoyo_aggregator), | ||
25 | [TOMOYO_ID_TRANSITION_CONTROL] = | ||
26 | sizeof(struct tomoyo_transition_control), | ||
27 | [TOMOYO_ID_MANAGER] = sizeof(struct tomoyo_manager), | ||
28 | /* [TOMOYO_ID_NAME] = "struct tomoyo_name"->size, */ | ||
29 | /* [TOMOYO_ID_ACL] = | ||
30 | tomoyo_acl_size["struct tomoyo_acl_info"->type], */ | ||
31 | [TOMOYO_ID_DOMAIN] = sizeof(struct tomoyo_domain_info), | ||
32 | }; | ||
33 | |||
34 | /* Size of a domain ACL element. */ | ||
35 | static const u8 tomoyo_acl_size[] = { | ||
36 | [TOMOYO_TYPE_PATH_ACL] = sizeof(struct tomoyo_path_acl), | ||
37 | [TOMOYO_TYPE_PATH2_ACL] = sizeof(struct tomoyo_path2_acl), | ||
38 | [TOMOYO_TYPE_PATH_NUMBER_ACL] = sizeof(struct tomoyo_path_number_acl), | ||
39 | [TOMOYO_TYPE_MKDEV_ACL] = sizeof(struct tomoyo_mkdev_acl), | ||
40 | [TOMOYO_TYPE_MOUNT_ACL] = sizeof(struct tomoyo_mount_acl), | ||
41 | }; | ||
42 | |||
43 | /** | ||
44 | * tomoyo_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/tomoyo/ users or not. | ||
45 | * | ||
46 | * @element: Pointer to "struct list_head". | ||
47 | * | ||
48 | * Returns true if @element is used by /sys/kernel/security/tomoyo/ users, | ||
49 | * false otherwise. | ||
50 | */ | ||
51 | static bool tomoyo_struct_used_by_io_buffer(const struct list_head *element) | ||
52 | { | ||
53 | struct tomoyo_io_buffer *head; | ||
54 | bool in_use = false; | ||
55 | |||
56 | spin_lock(&tomoyo_io_buffer_list_lock); | ||
57 | list_for_each_entry(head, &tomoyo_io_buffer_list, list) { | ||
58 | head->users++; | ||
59 | spin_unlock(&tomoyo_io_buffer_list_lock); | ||
60 | if (mutex_lock_interruptible(&head->io_sem)) { | ||
61 | in_use = true; | ||
62 | goto out; | ||
63 | } | ||
64 | if (head->r.domain == element || head->r.group == element || | ||
65 | head->r.acl == element || &head->w.domain->list == element) | ||
66 | in_use = true; | ||
67 | mutex_unlock(&head->io_sem); | ||
68 | out: | ||
69 | spin_lock(&tomoyo_io_buffer_list_lock); | ||
70 | head->users--; | ||
71 | if (in_use) | ||
72 | break; | ||
73 | } | ||
74 | spin_unlock(&tomoyo_io_buffer_list_lock); | ||
75 | return in_use; | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * tomoyo_name_used_by_io_buffer - Check whether the string is used by /sys/kernel/security/tomoyo/ users or not. | ||
80 | * | ||
81 | * @string: String to check. | ||
82 | * @size: Memory allocated for @string . | ||
83 | * | ||
84 | * Returns true if @string is used by /sys/kernel/security/tomoyo/ users, | ||
85 | * false otherwise. | ||
86 | */ | ||
87 | static bool tomoyo_name_used_by_io_buffer(const char *string, | ||
88 | const size_t size) | ||
89 | { | ||
90 | struct tomoyo_io_buffer *head; | ||
91 | bool in_use = false; | ||
92 | |||
93 | spin_lock(&tomoyo_io_buffer_list_lock); | ||
94 | list_for_each_entry(head, &tomoyo_io_buffer_list, list) { | ||
95 | int i; | ||
96 | head->users++; | ||
97 | spin_unlock(&tomoyo_io_buffer_list_lock); | ||
98 | if (mutex_lock_interruptible(&head->io_sem)) { | ||
99 | in_use = true; | ||
100 | goto out; | ||
101 | } | ||
102 | for (i = 0; i < TOMOYO_MAX_IO_READ_QUEUE; i++) { | ||
103 | const char *w = head->r.w[i]; | ||
104 | if (w < string || w > string + size) | ||
105 | continue; | ||
106 | in_use = true; | ||
107 | break; | ||
108 | } | ||
109 | mutex_unlock(&head->io_sem); | ||
110 | out: | ||
111 | spin_lock(&tomoyo_io_buffer_list_lock); | ||
112 | head->users--; | ||
113 | if (in_use) | ||
114 | break; | ||
115 | } | ||
116 | spin_unlock(&tomoyo_io_buffer_list_lock); | ||
117 | return in_use; | ||
118 | } | ||
119 | |||
120 | /* Structure for garbage collection. */ | ||
14 | struct tomoyo_gc { | 121 | struct tomoyo_gc { |
15 | struct list_head list; | 122 | struct list_head list; |
16 | enum tomoyo_policy_id type; | 123 | enum tomoyo_policy_id type; |
124 | size_t size; | ||
17 | struct list_head *element; | 125 | struct list_head *element; |
18 | }; | 126 | }; |
19 | static LIST_HEAD(tomoyo_gc_queue); | 127 | /* List of entries to be deleted. */ |
20 | static DEFINE_MUTEX(tomoyo_gc_mutex); | 128 | static LIST_HEAD(tomoyo_gc_list); |
129 | /* Length of tomoyo_gc_list. */ | ||
130 | static int tomoyo_gc_list_len; | ||
21 | 131 | ||
22 | /** | 132 | /** |
23 | * tomoyo_add_to_gc - Add an entry to to be deleted list. | 133 | * tomoyo_add_to_gc - Add an entry to to be deleted list. |
@@ -43,10 +153,42 @@ static bool tomoyo_add_to_gc(const int type, struct list_head *element) | |||
43 | if (!entry) | 153 | if (!entry) |
44 | return false; | 154 | return false; |
45 | entry->type = type; | 155 | entry->type = type; |
156 | if (type == TOMOYO_ID_ACL) | ||
157 | entry->size = tomoyo_acl_size[ | ||
158 | container_of(element, | ||
159 | typeof(struct tomoyo_acl_info), | ||
160 | list)->type]; | ||
161 | else if (type == TOMOYO_ID_NAME) | ||
162 | entry->size = strlen(container_of(element, | ||
163 | typeof(struct tomoyo_name), | ||
164 | head.list)->entry.name) + 1; | ||
165 | else | ||
166 | entry->size = tomoyo_element_size[type]; | ||
46 | entry->element = element; | 167 | entry->element = element; |
47 | list_add(&entry->list, &tomoyo_gc_queue); | 168 | list_add(&entry->list, &tomoyo_gc_list); |
48 | list_del_rcu(element); | 169 | list_del_rcu(element); |
49 | return true; | 170 | return tomoyo_gc_list_len++ < 128; |
171 | } | ||
172 | |||
173 | /** | ||
174 | * tomoyo_element_linked_by_gc - Validate next element of an entry. | ||
175 | * | ||
176 | * @element: Pointer to an element. | ||
177 | * @size: Size of @element in byte. | ||
178 | * | ||
179 | * Returns true if @element is linked by other elements in the garbage | ||
180 | * collector's queue, false otherwise. | ||
181 | */ | ||
182 | static bool tomoyo_element_linked_by_gc(const u8 *element, const size_t size) | ||
183 | { | ||
184 | struct tomoyo_gc *p; | ||
185 | list_for_each_entry(p, &tomoyo_gc_list, list) { | ||
186 | const u8 *ptr = (const u8 *) p->element->next; | ||
187 | if (ptr < element || element + size < ptr) | ||
188 | continue; | ||
189 | return true; | ||
190 | } | ||
191 | return false; | ||
50 | } | 192 | } |
51 | 193 | ||
52 | /** | 194 | /** |
@@ -151,6 +293,13 @@ static void tomoyo_del_acl(struct list_head *element) | |||
151 | } | 293 | } |
152 | } | 294 | } |
153 | 295 | ||
296 | /** | ||
297 | * tomoyo_del_domain - Delete members in "struct tomoyo_domain_info". | ||
298 | * | ||
299 | * @element: Pointer to "struct list_head". | ||
300 | * | ||
301 | * Returns true if deleted, false otherwise. | ||
302 | */ | ||
154 | static bool tomoyo_del_domain(struct list_head *element) | 303 | static bool tomoyo_del_domain(struct list_head *element) |
155 | { | 304 | { |
156 | struct tomoyo_domain_info *domain = | 305 | struct tomoyo_domain_info *domain = |
@@ -360,13 +509,44 @@ unlock: | |||
360 | mutex_unlock(&tomoyo_policy_lock); | 509 | mutex_unlock(&tomoyo_policy_lock); |
361 | } | 510 | } |
362 | 511 | ||
363 | static void tomoyo_kfree_entry(void) | 512 | /** |
513 | * tomoyo_kfree_entry - Delete entries in tomoyo_gc_list. | ||
514 | * | ||
515 | * Returns true if some entries were kfree()d, false otherwise. | ||
516 | */ | ||
517 | static bool tomoyo_kfree_entry(void) | ||
364 | { | 518 | { |
365 | struct tomoyo_gc *p; | 519 | struct tomoyo_gc *p; |
366 | struct tomoyo_gc *tmp; | 520 | struct tomoyo_gc *tmp; |
521 | bool result = false; | ||
367 | 522 | ||
368 | list_for_each_entry_safe(p, tmp, &tomoyo_gc_queue, list) { | 523 | list_for_each_entry_safe(p, tmp, &tomoyo_gc_list, list) { |
369 | struct list_head *element = p->element; | 524 | struct list_head *element = p->element; |
525 | |||
526 | /* | ||
527 | * list_del_rcu() in tomoyo_add_to_gc() guarantees that the | ||
528 | * list element became no longer reachable from the list which | ||
529 | * the element was originally on (e.g. tomoyo_domain_list). | ||
530 | * Also, synchronize_srcu() in tomoyo_gc_thread() guarantees | ||
531 | * that the list element became no longer referenced by syscall | ||
532 | * users. | ||
533 | * | ||
534 | * However, there are three users which may still be using the | ||
535 | * list element. We need to defer until all of these users | ||
536 | * forget the list element. | ||
537 | * | ||
538 | * Firstly, defer until "struct tomoyo_io_buffer"->r.{domain, | ||
539 | * group,acl} and "struct tomoyo_io_buffer"->w.domain forget | ||
540 | * the list element. | ||
541 | */ | ||
542 | if (tomoyo_struct_used_by_io_buffer(element)) | ||
543 | continue; | ||
544 | /* | ||
545 | * Secondly, defer until all other elements in the | ||
546 | * tomoyo_gc_list list forget the list element. | ||
547 | */ | ||
548 | if (tomoyo_element_linked_by_gc((const u8 *) element, p->size)) | ||
549 | continue; | ||
370 | switch (p->type) { | 550 | switch (p->type) { |
371 | case TOMOYO_ID_TRANSITION_CONTROL: | 551 | case TOMOYO_ID_TRANSITION_CONTROL: |
372 | tomoyo_del_transition_control(element); | 552 | tomoyo_del_transition_control(element); |
@@ -378,6 +558,14 @@ static void tomoyo_kfree_entry(void) | |||
378 | tomoyo_del_manager(element); | 558 | tomoyo_del_manager(element); |
379 | break; | 559 | break; |
380 | case TOMOYO_ID_NAME: | 560 | case TOMOYO_ID_NAME: |
561 | /* | ||
562 | * Thirdly, defer until all "struct tomoyo_io_buffer" | ||
563 | * ->r.w[] forget the list element. | ||
564 | */ | ||
565 | if (tomoyo_name_used_by_io_buffer( | ||
566 | container_of(element, typeof(struct tomoyo_name), | ||
567 | head.list)->entry.name, p->size)) | ||
568 | continue; | ||
381 | tomoyo_del_name(element); | 569 | tomoyo_del_name(element); |
382 | break; | 570 | break; |
383 | case TOMOYO_ID_ACL: | 571 | case TOMOYO_ID_ACL: |
@@ -402,7 +590,10 @@ static void tomoyo_kfree_entry(void) | |||
402 | tomoyo_memory_free(element); | 590 | tomoyo_memory_free(element); |
403 | list_del(&p->list); | 591 | list_del(&p->list); |
404 | kfree(p); | 592 | kfree(p); |
593 | tomoyo_gc_list_len--; | ||
594 | result = true; | ||
405 | } | 595 | } |
596 | return result; | ||
406 | } | 597 | } |
407 | 598 | ||
408 | /** | 599 | /** |
@@ -418,25 +609,70 @@ static void tomoyo_kfree_entry(void) | |||
418 | */ | 609 | */ |
419 | static int tomoyo_gc_thread(void *unused) | 610 | static int tomoyo_gc_thread(void *unused) |
420 | { | 611 | { |
612 | /* Garbage collector thread is exclusive. */ | ||
613 | static DEFINE_MUTEX(tomoyo_gc_mutex); | ||
614 | if (!mutex_trylock(&tomoyo_gc_mutex)) | ||
615 | goto out; | ||
421 | daemonize("GC for TOMOYO"); | 616 | daemonize("GC for TOMOYO"); |
422 | if (mutex_trylock(&tomoyo_gc_mutex)) { | 617 | do { |
423 | int i; | 618 | tomoyo_collect_entry(); |
424 | for (i = 0; i < 10; i++) { | 619 | if (list_empty(&tomoyo_gc_list)) |
425 | tomoyo_collect_entry(); | 620 | break; |
426 | if (list_empty(&tomoyo_gc_queue)) | 621 | synchronize_srcu(&tomoyo_ss); |
427 | break; | 622 | } while (tomoyo_kfree_entry()); |
428 | synchronize_srcu(&tomoyo_ss); | 623 | { |
429 | tomoyo_kfree_entry(); | 624 | struct tomoyo_io_buffer *head; |
625 | struct tomoyo_io_buffer *tmp; | ||
626 | |||
627 | spin_lock(&tomoyo_io_buffer_list_lock); | ||
628 | list_for_each_entry_safe(head, tmp, &tomoyo_io_buffer_list, | ||
629 | list) { | ||
630 | if (head->users) | ||
631 | continue; | ||
632 | list_del(&head->list); | ||
633 | kfree(head->read_buf); | ||
634 | kfree(head->write_buf); | ||
635 | kfree(head); | ||
430 | } | 636 | } |
431 | mutex_unlock(&tomoyo_gc_mutex); | 637 | spin_unlock(&tomoyo_io_buffer_list_lock); |
432 | } | 638 | } |
433 | do_exit(0); | 639 | mutex_unlock(&tomoyo_gc_mutex); |
640 | out: | ||
641 | /* This acts as do_exit(0). */ | ||
642 | return 0; | ||
434 | } | 643 | } |
435 | 644 | ||
436 | void tomoyo_run_gc(void) | 645 | /** |
646 | * tomoyo_notify_gc - Register/unregister /sys/kernel/security/tomoyo/ users. | ||
647 | * | ||
648 | * @head: Pointer to "struct tomoyo_io_buffer". | ||
649 | * @is_register: True if register, false if unregister. | ||
650 | * | ||
651 | * Returns nothing. | ||
652 | */ | ||
653 | void tomoyo_notify_gc(struct tomoyo_io_buffer *head, const bool is_register) | ||
437 | { | 654 | { |
438 | struct task_struct *task = kthread_create(tomoyo_gc_thread, NULL, | 655 | bool is_write = false; |
439 | "GC for TOMOYO"); | 656 | |
440 | if (!IS_ERR(task)) | 657 | spin_lock(&tomoyo_io_buffer_list_lock); |
441 | wake_up_process(task); | 658 | if (is_register) { |
659 | head->users = 1; | ||
660 | list_add(&head->list, &tomoyo_io_buffer_list); | ||
661 | } else { | ||
662 | is_write = head->write_buf != NULL; | ||
663 | if (!--head->users) { | ||
664 | list_del(&head->list); | ||
665 | kfree(head->read_buf); | ||
666 | kfree(head->write_buf); | ||
667 | kfree(head); | ||
668 | } | ||
669 | } | ||
670 | spin_unlock(&tomoyo_io_buffer_list_lock); | ||
671 | if (is_write) { | ||
672 | struct task_struct *task = kthread_create(tomoyo_gc_thread, | ||
673 | NULL, | ||
674 | "GC for TOMOYO"); | ||
675 | if (!IS_ERR(task)) | ||
676 | wake_up_process(task); | ||
677 | } | ||
442 | } | 678 | } |