aboutsummaryrefslogtreecommitdiffstats
path: root/fs/proc/proc_sysctl.c
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2012-01-21 16:34:05 -0500
committerEric W. Biederman <ebiederm@xmission.com>2012-01-24 19:40:27 -0500
commit7c60c48f58a78195acc1f71c9a9d01958c02ab89 (patch)
tree7d1a66abc2510aa474105a747fdcd08b033f2b36 /fs/proc/proc_sysctl.c
parentf728019bb72e655680c02ad1829323054a8e875f (diff)
sysctl: Improve the sysctl sanity checks
- Stop validating subdirectories now that we only register leaf tables - Cleanup and improve the duplicate filename check. * Run the duplicate filename check under the sysctl_lock to guarantee we never add duplicate names. * Reduce the duplicate filename check to nearly O(M*N) where M is the number of entries in tthe table we are registering and N is the number of entries in the directory before we got there. - Move the duplicate filename check into it's own function and call it directtly from __register_sysctl_table - Kill the config option as the sanity checks are now cheap enough the config option is unnecessary. The original reason for the config option was because we had a huge table used to verify the proc filename to binary sysctl mapping. That table has now evolved into the binary_sysctl translation layer and is no longer part of the sysctl_check code. - Tighten up the permission checks. Guarnateeing that files only have read or write permissions. - Removed redudant check for parents having a procname as now everything has a procname. - Generalize the backtrace logic so that we print a backtrace from any failure of __register_sysctl_table that was not caused by a memmory allocation failure. The backtrace allows us to track down who erroneously registered a sysctl table. Bechmark before (CONFIG_SYSCTL_CHECK=y): make-dummies 0 999 -> 12s rmmod dummy -> 0.08s Bechmark before (CONFIG_SYSCTL_CHECK=n): make-dummies 0 999 -> 0.7s rmmod dummy -> 0.06s make-dummies 0 99999 -> 1m13s rmmod dummy -> 0.38s Benchmark after: make-dummies 0 999 -> 0.65s rmmod dummy -> 0.055s make-dummies 0 9999 -> 1m10s rmmod dummy -> 0.39s The sysctl sanity checks now impose no measurable cost. Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
Diffstat (limited to 'fs/proc/proc_sysctl.c')
-rw-r--r--fs/proc/proc_sysctl.c222
1 files changed, 86 insertions, 136 deletions
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 6bab2ae9e395..a492ff60e071 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -726,160 +726,106 @@ static void try_attach(struct ctl_table_header *p, struct ctl_table_header *q)
726 } 726 }
727} 727}
728 728
729#ifdef CONFIG_SYSCTL_SYSCALL_CHECK 729static int sysctl_check_table_dups(const char *path, struct ctl_table *old,
730static int sysctl_depth(struct ctl_table *table) 730 struct ctl_table *table)
731{ 731{
732 struct ctl_table *tmp; 732 struct ctl_table *entry, *test;
733 int depth; 733 int error = 0;
734
735 depth = 0;
736 for (tmp = table; tmp->parent; tmp = tmp->parent)
737 depth++;
738 734
739 return depth; 735 for (entry = old; entry->procname; entry++) {
736 for (test = table; test->procname; test++) {
737 if (strcmp(entry->procname, test->procname) == 0) {
738 printk(KERN_ERR "sysctl duplicate entry: %s/%s\n",
739 path, test->procname);
740 error = -EEXIST;
741 }
742 }
743 }
744 return error;
740} 745}
741 746
742static struct ctl_table *sysctl_parent(struct ctl_table *table, int n) 747static int sysctl_check_dups(struct nsproxy *namespaces,
748 struct ctl_table_header *header,
749 const char *path, struct ctl_table *table)
743{ 750{
744 int i; 751 struct ctl_table_root *root;
752 struct ctl_table_set *set;
753 struct ctl_table_header *dir_head, *head;
754 struct ctl_table *dir_table;
755 int error = 0;
745 756
746 for (i = 0; table && i < n; i++) 757 /* No dups if we are the only member of our directory */
747 table = table->parent; 758 if (header->attached_by != table)
759 return 0;
748 760
749 return table; 761 dir_head = header->parent;
750} 762 dir_table = header->attached_to;
751 763
764 error = sysctl_check_table_dups(path, dir_table, table);
752 765
753static void sysctl_print_path(struct ctl_table *table) 766 root = &sysctl_table_root;
754{ 767 do {
755 struct ctl_table *tmp; 768 set = lookup_header_set(root, namespaces);
756 int depth, i;
757 depth = sysctl_depth(table);
758 if (table->procname) {
759 for (i = depth; i >= 0; i--) {
760 tmp = sysctl_parent(table, i);
761 printk("/%s", tmp->procname?tmp->procname:"");
762 }
763 }
764 printk(" ");
765}
766 769
767static struct ctl_table *sysctl_check_lookup(struct nsproxy *namespaces, 770 list_for_each_entry(head, &set->list, ctl_entry) {
768 struct ctl_table *table) 771 if (head->unregistering)
769{
770 struct ctl_table_header *head;
771 struct ctl_table *ref, *test;
772 int depth, cur_depth;
773
774 depth = sysctl_depth(table);
775
776 for (head = __sysctl_head_next(namespaces, NULL); head;
777 head = __sysctl_head_next(namespaces, head)) {
778 cur_depth = depth;
779 ref = head->ctl_table;
780repeat:
781 test = sysctl_parent(table, cur_depth);
782 for (; ref->procname; ref++) {
783 int match = 0;
784 if (cur_depth && !ref->child)
785 continue; 772 continue;
786 773 if (head->attached_to != dir_table)
787 if (test->procname && ref->procname && 774 continue;
788 (strcmp(test->procname, ref->procname) == 0)) 775 error = sysctl_check_table_dups(path, head->attached_by,
789 match++; 776 table);
790
791 if (match) {
792 if (cur_depth != 0) {
793 cur_depth--;
794 ref = ref->child;
795 goto repeat;
796 }
797 goto out;
798 }
799 } 777 }
800 } 778 root = list_entry(root->root_list.next,
801 ref = NULL; 779 struct ctl_table_root, root_list);
802out: 780 } while (root != &sysctl_table_root);
803 sysctl_head_finish(head); 781 return error;
804 return ref;
805} 782}
806 783
807static void set_fail(const char **fail, struct ctl_table *table, const char *str) 784static int sysctl_err(const char *path, struct ctl_table *table, char *fmt, ...)
808{ 785{
809 if (*fail) { 786 struct va_format vaf;
810 printk(KERN_ERR "sysctl table check failed: "); 787 va_list args;
811 sysctl_print_path(table);
812 printk(" %s\n", *fail);
813 dump_stack();
814 }
815 *fail = str;
816}
817 788
818static void sysctl_check_leaf(struct nsproxy *namespaces, 789 va_start(args, fmt);
819 struct ctl_table *table, const char **fail) 790 vaf.fmt = fmt;
820{ 791 vaf.va = &args;
821 struct ctl_table *ref; 792
793 printk(KERN_ERR "sysctl table check failed: %s/%s %pV\n",
794 path, table->procname, &vaf);
822 795
823 ref = sysctl_check_lookup(namespaces, table); 796 va_end(args);
824 if (ref && (ref != table)) 797 return -EINVAL;
825 set_fail(fail, table, "Sysctl already exists");
826} 798}
827 799
828static int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *table) 800static int sysctl_check_table(const char *path, struct ctl_table *table)
829{ 801{
830 int error = 0; 802 int err = 0;
831 for (; table->procname; table++) { 803 for (; table->procname; table++) {
832 const char *fail = NULL;
833
834 if (table->parent) {
835 if (!table->parent->procname)
836 set_fail(&fail, table, "Parent without procname");
837 }
838 if (table->child) {
839 if (table->data)
840 set_fail(&fail, table, "Directory with data?");
841 if (table->maxlen)
842 set_fail(&fail, table, "Directory with maxlen?");
843 if ((table->mode & (S_IRUGO|S_IXUGO)) != table->mode)
844 set_fail(&fail, table, "Writable sysctl directory");
845 if (table->proc_handler)
846 set_fail(&fail, table, "Directory with proc_handler");
847 if (table->extra1)
848 set_fail(&fail, table, "Directory with extra1");
849 if (table->extra2)
850 set_fail(&fail, table, "Directory with extra2");
851 } else {
852 if ((table->proc_handler == proc_dostring) ||
853 (table->proc_handler == proc_dointvec) ||
854 (table->proc_handler == proc_dointvec_minmax) ||
855 (table->proc_handler == proc_dointvec_jiffies) ||
856 (table->proc_handler == proc_dointvec_userhz_jiffies) ||
857 (table->proc_handler == proc_dointvec_ms_jiffies) ||
858 (table->proc_handler == proc_doulongvec_minmax) ||
859 (table->proc_handler == proc_doulongvec_ms_jiffies_minmax)) {
860 if (!table->data)
861 set_fail(&fail, table, "No data");
862 if (!table->maxlen)
863 set_fail(&fail, table, "No maxlen");
864 }
865#ifdef CONFIG_PROC_SYSCTL
866 if (!table->proc_handler)
867 set_fail(&fail, table, "No proc_handler");
868#endif
869 sysctl_check_leaf(namespaces, table, &fail);
870 }
871 if (table->mode > 0777)
872 set_fail(&fail, table, "bogus .mode");
873 if (fail) {
874 set_fail(&fail, table, NULL);
875 error = -EINVAL;
876 }
877 if (table->child) 804 if (table->child)
878 error |= sysctl_check_table(namespaces, table->child); 805 err = sysctl_err(path, table, "Not a file");
806
807 if ((table->proc_handler == proc_dostring) ||
808 (table->proc_handler == proc_dointvec) ||
809 (table->proc_handler == proc_dointvec_minmax) ||
810 (table->proc_handler == proc_dointvec_jiffies) ||
811 (table->proc_handler == proc_dointvec_userhz_jiffies) ||
812 (table->proc_handler == proc_dointvec_ms_jiffies) ||
813 (table->proc_handler == proc_doulongvec_minmax) ||
814 (table->proc_handler == proc_doulongvec_ms_jiffies_minmax)) {
815 if (!table->data)
816 err = sysctl_err(path, table, "No data");
817 if (!table->maxlen)
818 err = sysctl_err(path, table, "No maxlen");
819 }
820 if (!table->proc_handler)
821 err = sysctl_err(path, table, "No proc_handler");
822
823 if ((table->mode & (S_IRUGO|S_IWUGO)) != table->mode)
824 err = sysctl_err(path, table, "bogus .mode 0%o",
825 table->mode);
879 } 826 }
880 return error; 827 return err;
881} 828}
882#endif /* CONFIG_SYSCTL_SYSCALL_CHECK */
883 829
884/** 830/**
885 * __register_sysctl_table - register a leaf sysctl table 831 * __register_sysctl_table - register a leaf sysctl table
@@ -1003,12 +949,8 @@ struct ctl_table_header *__register_sysctl_table(
1003 header->root = root; 949 header->root = root;
1004 sysctl_set_parent(NULL, header->ctl_table); 950 sysctl_set_parent(NULL, header->ctl_table);
1005 header->count = 1; 951 header->count = 1;
1006#ifdef CONFIG_SYSCTL_SYSCALL_CHECK 952 if (sysctl_check_table(path, table))
1007 if (sysctl_check_table(namespaces, header->ctl_table)) { 953 goto fail;
1008 kfree(header);
1009 return NULL;
1010 }
1011#endif
1012 spin_lock(&sysctl_lock); 954 spin_lock(&sysctl_lock);
1013 header->set = lookup_header_set(root, namespaces); 955 header->set = lookup_header_set(root, namespaces);
1014 header->attached_by = header->ctl_table; 956 header->attached_by = header->ctl_table;
@@ -1029,11 +971,19 @@ struct ctl_table_header *__register_sysctl_table(
1029 struct ctl_table_root, root_list); 971 struct ctl_table_root, root_list);
1030 set = lookup_header_set(root, namespaces); 972 set = lookup_header_set(root, namespaces);
1031 } 973 }
974 if (sysctl_check_dups(namespaces, header, path, table))
975 goto fail_locked;
1032 header->parent->count++; 976 header->parent->count++;
1033 list_add_tail(&header->ctl_entry, &header->set->list); 977 list_add_tail(&header->ctl_entry, &header->set->list);
1034 spin_unlock(&sysctl_lock); 978 spin_unlock(&sysctl_lock);
1035 979
1036 return header; 980 return header;
981fail_locked:
982 spin_unlock(&sysctl_lock);
983fail:
984 kfree(header);
985 dump_stack();
986 return NULL;
1037} 987}
1038 988
1039static char *append_path(const char *path, char *pos, const char *name) 989static char *append_path(const char *path, char *pos, const char *name)