diff options
author | Imre Palik <imrep@amazon.de> | 2015-02-23 15:37:59 -0500 |
---|---|---|
committer | Paul Moore <pmoore@redhat.com> | 2015-02-23 15:37:59 -0500 |
commit | f1aaf26224bee779012aab136e5373ce3487982c (patch) | |
tree | d0c309cc10f29c2643e3214783fd65d835ba13bd /kernel | |
parent | 2fded7f44b8fcf79e274c3f0cfbd0298f95308f3 (diff) |
audit: move the tree pruning to a dedicated thread
When file auditing is enabled, during a low memory situation, a memory
allocation with __GFP_FS can lead to pruning the inode cache. Which can,
in turn lead to audit_tree_freeing_mark() being called. This can call
audit_schedule_prune(), that tries to fork a pruning thread, and
waits until the thread is created. But forking needs memory, and the
memory allocations there are done with __GFP_FS.
So we are waiting merrily for some __GFP_FS memory allocations to complete,
while holding some filesystem locks. This can take a while ...
This patch creates a single thread for pruning the tree from
audit_add_tree_rule(), and thus avoids the deadlock that the on-demand
thread creation can cause.
Reported-by: Matt Wilson <msw@amazon.com>
Cc: Matt Wilson <msw@amazon.com>
Signed-off-by: Imre Palik <imrep@amazon.de>
Reviewed-by: Richard Guy Briggs <rgb@redhat.com>
Signed-off-by: Paul Moore <pmoore@redhat.com>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/audit_tree.c | 88 |
1 files changed, 60 insertions, 28 deletions
diff --git a/kernel/audit_tree.c b/kernel/audit_tree.c index 80f29e015570..415072c8e875 100644 --- a/kernel/audit_tree.c +++ b/kernel/audit_tree.c | |||
@@ -37,6 +37,7 @@ struct audit_chunk { | |||
37 | 37 | ||
38 | static LIST_HEAD(tree_list); | 38 | static LIST_HEAD(tree_list); |
39 | static LIST_HEAD(prune_list); | 39 | static LIST_HEAD(prune_list); |
40 | static struct task_struct *prune_thread; | ||
40 | 41 | ||
41 | /* | 42 | /* |
42 | * One struct chunk is attached to each inode of interest. | 43 | * One struct chunk is attached to each inode of interest. |
@@ -651,6 +652,57 @@ static int tag_mount(struct vfsmount *mnt, void *arg) | |||
651 | return tag_chunk(mnt->mnt_root->d_inode, arg); | 652 | return tag_chunk(mnt->mnt_root->d_inode, arg); |
652 | } | 653 | } |
653 | 654 | ||
655 | /* | ||
656 | * That gets run when evict_chunk() ends up needing to kill audit_tree. | ||
657 | * Runs from a separate thread. | ||
658 | */ | ||
659 | static int prune_tree_thread(void *unused) | ||
660 | { | ||
661 | for (;;) { | ||
662 | set_current_state(TASK_INTERRUPTIBLE); | ||
663 | if (list_empty(&prune_list)) | ||
664 | schedule(); | ||
665 | __set_current_state(TASK_RUNNING); | ||
666 | |||
667 | mutex_lock(&audit_cmd_mutex); | ||
668 | mutex_lock(&audit_filter_mutex); | ||
669 | |||
670 | while (!list_empty(&prune_list)) { | ||
671 | struct audit_tree *victim; | ||
672 | |||
673 | victim = list_entry(prune_list.next, | ||
674 | struct audit_tree, list); | ||
675 | list_del_init(&victim->list); | ||
676 | |||
677 | mutex_unlock(&audit_filter_mutex); | ||
678 | |||
679 | prune_one(victim); | ||
680 | |||
681 | mutex_lock(&audit_filter_mutex); | ||
682 | } | ||
683 | |||
684 | mutex_unlock(&audit_filter_mutex); | ||
685 | mutex_unlock(&audit_cmd_mutex); | ||
686 | } | ||
687 | return 0; | ||
688 | } | ||
689 | |||
690 | static int audit_launch_prune(void) | ||
691 | { | ||
692 | if (prune_thread) | ||
693 | return 0; | ||
694 | prune_thread = kthread_create(prune_tree_thread, NULL, | ||
695 | "audit_prune_tree"); | ||
696 | if (IS_ERR(prune_thread)) { | ||
697 | pr_err("cannot start thread audit_prune_tree"); | ||
698 | prune_thread = NULL; | ||
699 | return -ENOMEM; | ||
700 | } else { | ||
701 | wake_up_process(prune_thread); | ||
702 | return 0; | ||
703 | } | ||
704 | } | ||
705 | |||
654 | /* called with audit_filter_mutex */ | 706 | /* called with audit_filter_mutex */ |
655 | int audit_add_tree_rule(struct audit_krule *rule) | 707 | int audit_add_tree_rule(struct audit_krule *rule) |
656 | { | 708 | { |
@@ -674,6 +726,12 @@ int audit_add_tree_rule(struct audit_krule *rule) | |||
674 | /* do not set rule->tree yet */ | 726 | /* do not set rule->tree yet */ |
675 | mutex_unlock(&audit_filter_mutex); | 727 | mutex_unlock(&audit_filter_mutex); |
676 | 728 | ||
729 | if (unlikely(!prune_thread)) { | ||
730 | err = audit_launch_prune(); | ||
731 | if (err) | ||
732 | goto Err; | ||
733 | } | ||
734 | |||
677 | err = kern_path(tree->pathname, 0, &path); | 735 | err = kern_path(tree->pathname, 0, &path); |
678 | if (err) | 736 | if (err) |
679 | goto Err; | 737 | goto Err; |
@@ -811,36 +869,10 @@ int audit_tag_tree(char *old, char *new) | |||
811 | return failed; | 869 | return failed; |
812 | } | 870 | } |
813 | 871 | ||
814 | /* | ||
815 | * That gets run when evict_chunk() ends up needing to kill audit_tree. | ||
816 | * Runs from a separate thread. | ||
817 | */ | ||
818 | static int prune_tree_thread(void *unused) | ||
819 | { | ||
820 | mutex_lock(&audit_cmd_mutex); | ||
821 | mutex_lock(&audit_filter_mutex); | ||
822 | |||
823 | while (!list_empty(&prune_list)) { | ||
824 | struct audit_tree *victim; | ||
825 | |||
826 | victim = list_entry(prune_list.next, struct audit_tree, list); | ||
827 | list_del_init(&victim->list); | ||
828 | |||
829 | mutex_unlock(&audit_filter_mutex); | ||
830 | |||
831 | prune_one(victim); | ||
832 | |||
833 | mutex_lock(&audit_filter_mutex); | ||
834 | } | ||
835 | |||
836 | mutex_unlock(&audit_filter_mutex); | ||
837 | mutex_unlock(&audit_cmd_mutex); | ||
838 | return 0; | ||
839 | } | ||
840 | 872 | ||
841 | static void audit_schedule_prune(void) | 873 | static void audit_schedule_prune(void) |
842 | { | 874 | { |
843 | kthread_run(prune_tree_thread, NULL, "audit_prune_tree"); | 875 | wake_up_process(prune_thread); |
844 | } | 876 | } |
845 | 877 | ||
846 | /* | 878 | /* |
@@ -907,9 +939,9 @@ static void evict_chunk(struct audit_chunk *chunk) | |||
907 | for (n = 0; n < chunk->count; n++) | 939 | for (n = 0; n < chunk->count; n++) |
908 | list_del_init(&chunk->owners[n].list); | 940 | list_del_init(&chunk->owners[n].list); |
909 | spin_unlock(&hash_lock); | 941 | spin_unlock(&hash_lock); |
942 | mutex_unlock(&audit_filter_mutex); | ||
910 | if (need_prune) | 943 | if (need_prune) |
911 | audit_schedule_prune(); | 944 | audit_schedule_prune(); |
912 | mutex_unlock(&audit_filter_mutex); | ||
913 | } | 945 | } |
914 | 946 | ||
915 | static int audit_tree_handle_event(struct fsnotify_group *group, | 947 | static int audit_tree_handle_event(struct fsnotify_group *group, |