diff options
author | Stephen Wilson <wilsons@start.ca> | 2011-03-13 15:49:23 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2011-03-23 16:36:59 -0400 |
commit | 8b0db9db19858b08c46a84540acfd35f6e6487b8 (patch) | |
tree | f8cad66b43b21ac8cc58c6173b86aaa9ee3d4b5f /fs/proc | |
parent | 18f661bcf898742212182d75f22f05b048cc04bb (diff) |
proc: make check_mem_permission() return an mm_struct on success
This change allows us to take advantage of access_remote_vm(), which in turn
eliminates a security issue with the mem_write() implementation.
The previous implementation of mem_write() was insecure since the target task
could exec a setuid-root binary between the permission check and the actual
write. Holding a reference to the target mm_struct eliminates this
vulnerability.
Signed-off-by: Stephen Wilson <wilsons@start.ca>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/proc')
-rw-r--r-- | fs/proc/base.c | 58 |
1 files changed, 34 insertions, 24 deletions
diff --git a/fs/proc/base.c b/fs/proc/base.c index 013f116b3223..e34c3c33b2de 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c | |||
@@ -191,14 +191,20 @@ static int proc_root_link(struct inode *inode, struct path *path) | |||
191 | return result; | 191 | return result; |
192 | } | 192 | } |
193 | 193 | ||
194 | static int __check_mem_permission(struct task_struct *task) | 194 | static struct mm_struct *__check_mem_permission(struct task_struct *task) |
195 | { | 195 | { |
196 | struct mm_struct *mm; | ||
197 | |||
198 | mm = get_task_mm(task); | ||
199 | if (!mm) | ||
200 | return ERR_PTR(-EINVAL); | ||
201 | |||
196 | /* | 202 | /* |
197 | * A task can always look at itself, in case it chooses | 203 | * A task can always look at itself, in case it chooses |
198 | * to use system calls instead of load instructions. | 204 | * to use system calls instead of load instructions. |
199 | */ | 205 | */ |
200 | if (task == current) | 206 | if (task == current) |
201 | return 0; | 207 | return mm; |
202 | 208 | ||
203 | /* | 209 | /* |
204 | * If current is actively ptrace'ing, and would also be | 210 | * If current is actively ptrace'ing, and would also be |
@@ -210,20 +216,23 @@ static int __check_mem_permission(struct task_struct *task) | |||
210 | match = (tracehook_tracer_task(task) == current); | 216 | match = (tracehook_tracer_task(task) == current); |
211 | rcu_read_unlock(); | 217 | rcu_read_unlock(); |
212 | if (match && ptrace_may_access(task, PTRACE_MODE_ATTACH)) | 218 | if (match && ptrace_may_access(task, PTRACE_MODE_ATTACH)) |
213 | return 0; | 219 | return mm; |
214 | } | 220 | } |
215 | 221 | ||
216 | /* | 222 | /* |
217 | * Noone else is allowed. | 223 | * Noone else is allowed. |
218 | */ | 224 | */ |
219 | return -EPERM; | 225 | mmput(mm); |
226 | return ERR_PTR(-EPERM); | ||
220 | } | 227 | } |
221 | 228 | ||
222 | /* | 229 | /* |
223 | * Return zero if current may access user memory in @task, -error if not. | 230 | * If current may access user memory in @task return a reference to the |
231 | * corresponding mm, otherwise ERR_PTR. | ||
224 | */ | 232 | */ |
225 | static int check_mem_permission(struct task_struct *task) | 233 | static struct mm_struct *check_mem_permission(struct task_struct *task) |
226 | { | 234 | { |
235 | struct mm_struct *mm; | ||
227 | int err; | 236 | int err; |
228 | 237 | ||
229 | /* | 238 | /* |
@@ -232,12 +241,12 @@ static int check_mem_permission(struct task_struct *task) | |||
232 | */ | 241 | */ |
233 | err = mutex_lock_killable(&task->signal->cred_guard_mutex); | 242 | err = mutex_lock_killable(&task->signal->cred_guard_mutex); |
234 | if (err) | 243 | if (err) |
235 | return err; | 244 | return ERR_PTR(err); |
236 | 245 | ||
237 | err = __check_mem_permission(task); | 246 | mm = __check_mem_permission(task); |
238 | mutex_unlock(&task->signal->cred_guard_mutex); | 247 | mutex_unlock(&task->signal->cred_guard_mutex); |
239 | 248 | ||
240 | return err; | 249 | return mm; |
241 | } | 250 | } |
242 | 251 | ||
243 | struct mm_struct *mm_for_maps(struct task_struct *task) | 252 | struct mm_struct *mm_for_maps(struct task_struct *task) |
@@ -795,18 +804,14 @@ static ssize_t mem_read(struct file * file, char __user * buf, | |||
795 | if (!task) | 804 | if (!task) |
796 | goto out_no_task; | 805 | goto out_no_task; |
797 | 806 | ||
798 | if (check_mem_permission(task)) | ||
799 | goto out; | ||
800 | |||
801 | ret = -ENOMEM; | 807 | ret = -ENOMEM; |
802 | page = (char *)__get_free_page(GFP_TEMPORARY); | 808 | page = (char *)__get_free_page(GFP_TEMPORARY); |
803 | if (!page) | 809 | if (!page) |
804 | goto out; | 810 | goto out; |
805 | 811 | ||
806 | ret = 0; | 812 | mm = check_mem_permission(task); |
807 | 813 | ret = PTR_ERR(mm); | |
808 | mm = get_task_mm(task); | 814 | if (IS_ERR(mm)) |
809 | if (!mm) | ||
810 | goto out_free; | 815 | goto out_free; |
811 | 816 | ||
812 | ret = -EIO; | 817 | ret = -EIO; |
@@ -820,8 +825,8 @@ static ssize_t mem_read(struct file * file, char __user * buf, | |||
820 | int this_len, retval; | 825 | int this_len, retval; |
821 | 826 | ||
822 | this_len = (count > PAGE_SIZE) ? PAGE_SIZE : count; | 827 | this_len = (count > PAGE_SIZE) ? PAGE_SIZE : count; |
823 | retval = access_process_vm(task, src, page, this_len, 0); | 828 | retval = access_remote_vm(mm, src, page, this_len, 0); |
824 | if (!retval || check_mem_permission(task)) { | 829 | if (!retval) { |
825 | if (!ret) | 830 | if (!ret) |
826 | ret = -EIO; | 831 | ret = -EIO; |
827 | break; | 832 | break; |
@@ -860,22 +865,25 @@ static ssize_t mem_write(struct file * file, const char __user *buf, | |||
860 | char *page; | 865 | char *page; |
861 | struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode); | 866 | struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode); |
862 | unsigned long dst = *ppos; | 867 | unsigned long dst = *ppos; |
868 | struct mm_struct *mm; | ||
863 | 869 | ||
864 | copied = -ESRCH; | 870 | copied = -ESRCH; |
865 | if (!task) | 871 | if (!task) |
866 | goto out_no_task; | 872 | goto out_no_task; |
867 | 873 | ||
868 | if (check_mem_permission(task)) | 874 | mm = check_mem_permission(task); |
869 | goto out; | 875 | copied = PTR_ERR(mm); |
876 | if (IS_ERR(mm)) | ||
877 | goto out_task; | ||
870 | 878 | ||
871 | copied = -EIO; | 879 | copied = -EIO; |
872 | if (file->private_data != (void *)((long)current->self_exec_id)) | 880 | if (file->private_data != (void *)((long)current->self_exec_id)) |
873 | goto out; | 881 | goto out_mm; |
874 | 882 | ||
875 | copied = -ENOMEM; | 883 | copied = -ENOMEM; |
876 | page = (char *)__get_free_page(GFP_TEMPORARY); | 884 | page = (char *)__get_free_page(GFP_TEMPORARY); |
877 | if (!page) | 885 | if (!page) |
878 | goto out; | 886 | goto out_mm; |
879 | 887 | ||
880 | copied = 0; | 888 | copied = 0; |
881 | while (count > 0) { | 889 | while (count > 0) { |
@@ -886,7 +894,7 @@ static ssize_t mem_write(struct file * file, const char __user *buf, | |||
886 | copied = -EFAULT; | 894 | copied = -EFAULT; |
887 | break; | 895 | break; |
888 | } | 896 | } |
889 | retval = access_process_vm(task, dst, page, this_len, 1); | 897 | retval = access_remote_vm(mm, dst, page, this_len, 1); |
890 | if (!retval) { | 898 | if (!retval) { |
891 | if (!copied) | 899 | if (!copied) |
892 | copied = -EIO; | 900 | copied = -EIO; |
@@ -899,7 +907,9 @@ static ssize_t mem_write(struct file * file, const char __user *buf, | |||
899 | } | 907 | } |
900 | *ppos = dst; | 908 | *ppos = dst; |
901 | free_page((unsigned long) page); | 909 | free_page((unsigned long) page); |
902 | out: | 910 | out_mm: |
911 | mmput(mm); | ||
912 | out_task: | ||
903 | put_task_struct(task); | 913 | put_task_struct(task); |
904 | out_no_task: | 914 | out_no_task: |
905 | return copied; | 915 | return copied; |