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; |
