diff options
author | Hugh Dickins <hugh@veritas.com> | 2009-03-28 19:21:27 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-03-28 20:30:00 -0400 |
commit | 7c2c7d993044cddc5010f6f429b100c63bc7dffb (patch) | |
tree | b92a6daf7c11f9a53de6fed07512fe02cd5b4a68 | |
parent | e426b64c412aaa3e9eb3e4b261dc5be0d5a83e78 (diff) |
fix setuid sometimes wouldn't
check_unsafe_exec() also notes whether the fs_struct is being
shared by more threads than will get killed by the exec, and if so
sets LSM_UNSAFE_SHARE to make bprm_set_creds() careful about euid.
But /proc/<pid>/cwd and /proc/<pid>/root lookups make transient
use of get_fs_struct(), which also raises that sharing count.
This might occasionally cause a setuid program not to change euid,
in the same way as happened with files->count (check_unsafe_exec
also looks at sighand->count, but /proc doesn't raise that one).
We'd prefer exec not to unshare fs_struct: so fix this in procfs,
replacing get_fs_struct() by get_fs_path(), which does path_get
while still holding task_lock, instead of raising fs->count.
Signed-off-by: Hugh Dickins <hugh@veritas.com>
Cc: stable@kernel.org
___
fs/proc/base.c | 50 +++++++++++++++--------------------------------
1 file changed, 16 insertions(+), 34 deletions(-)
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | fs/proc/base.c | 50 |
1 files changed, 16 insertions, 34 deletions
diff --git a/fs/proc/base.c b/fs/proc/base.c index aef6d55b7de6..e0afd326b688 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c | |||
@@ -146,15 +146,22 @@ static unsigned int pid_entry_count_dirs(const struct pid_entry *entries, | |||
146 | return count; | 146 | return count; |
147 | } | 147 | } |
148 | 148 | ||
149 | static struct fs_struct *get_fs_struct(struct task_struct *task) | 149 | static int get_fs_path(struct task_struct *task, struct path *path, bool root) |
150 | { | 150 | { |
151 | struct fs_struct *fs; | 151 | struct fs_struct *fs; |
152 | int result = -ENOENT; | ||
153 | |||
152 | task_lock(task); | 154 | task_lock(task); |
153 | fs = task->fs; | 155 | fs = task->fs; |
154 | if(fs) | 156 | if (fs) { |
155 | atomic_inc(&fs->count); | 157 | read_lock(&fs->lock); |
158 | *path = root ? fs->root : fs->pwd; | ||
159 | path_get(path); | ||
160 | read_unlock(&fs->lock); | ||
161 | result = 0; | ||
162 | } | ||
156 | task_unlock(task); | 163 | task_unlock(task); |
157 | return fs; | 164 | return result; |
158 | } | 165 | } |
159 | 166 | ||
160 | static int get_nr_threads(struct task_struct *tsk) | 167 | static int get_nr_threads(struct task_struct *tsk) |
@@ -172,42 +179,24 @@ static int get_nr_threads(struct task_struct *tsk) | |||
172 | static int proc_cwd_link(struct inode *inode, struct path *path) | 179 | static int proc_cwd_link(struct inode *inode, struct path *path) |
173 | { | 180 | { |
174 | struct task_struct *task = get_proc_task(inode); | 181 | struct task_struct *task = get_proc_task(inode); |
175 | struct fs_struct *fs = NULL; | ||
176 | int result = -ENOENT; | 182 | int result = -ENOENT; |
177 | 183 | ||
178 | if (task) { | 184 | if (task) { |
179 | fs = get_fs_struct(task); | 185 | result = get_fs_path(task, path, 0); |
180 | put_task_struct(task); | 186 | put_task_struct(task); |
181 | } | 187 | } |
182 | if (fs) { | ||
183 | read_lock(&fs->lock); | ||
184 | *path = fs->pwd; | ||
185 | path_get(&fs->pwd); | ||
186 | read_unlock(&fs->lock); | ||
187 | result = 0; | ||
188 | put_fs_struct(fs); | ||
189 | } | ||
190 | return result; | 188 | return result; |
191 | } | 189 | } |
192 | 190 | ||
193 | static int proc_root_link(struct inode *inode, struct path *path) | 191 | static int proc_root_link(struct inode *inode, struct path *path) |
194 | { | 192 | { |
195 | struct task_struct *task = get_proc_task(inode); | 193 | struct task_struct *task = get_proc_task(inode); |
196 | struct fs_struct *fs = NULL; | ||
197 | int result = -ENOENT; | 194 | int result = -ENOENT; |
198 | 195 | ||
199 | if (task) { | 196 | if (task) { |
200 | fs = get_fs_struct(task); | 197 | result = get_fs_path(task, path, 1); |
201 | put_task_struct(task); | 198 | put_task_struct(task); |
202 | } | 199 | } |
203 | if (fs) { | ||
204 | read_lock(&fs->lock); | ||
205 | *path = fs->root; | ||
206 | path_get(&fs->root); | ||
207 | read_unlock(&fs->lock); | ||
208 | result = 0; | ||
209 | put_fs_struct(fs); | ||
210 | } | ||
211 | return result; | 200 | return result; |
212 | } | 201 | } |
213 | 202 | ||
@@ -596,7 +585,6 @@ static int mounts_open_common(struct inode *inode, struct file *file, | |||
596 | struct task_struct *task = get_proc_task(inode); | 585 | struct task_struct *task = get_proc_task(inode); |
597 | struct nsproxy *nsp; | 586 | struct nsproxy *nsp; |
598 | struct mnt_namespace *ns = NULL; | 587 | struct mnt_namespace *ns = NULL; |
599 | struct fs_struct *fs = NULL; | ||
600 | struct path root; | 588 | struct path root; |
601 | struct proc_mounts *p; | 589 | struct proc_mounts *p; |
602 | int ret = -EINVAL; | 590 | int ret = -EINVAL; |
@@ -610,22 +598,16 @@ static int mounts_open_common(struct inode *inode, struct file *file, | |||
610 | get_mnt_ns(ns); | 598 | get_mnt_ns(ns); |
611 | } | 599 | } |
612 | rcu_read_unlock(); | 600 | rcu_read_unlock(); |
613 | if (ns) | 601 | if (ns && get_fs_path(task, &root, 1) == 0) |
614 | fs = get_fs_struct(task); | 602 | ret = 0; |
615 | put_task_struct(task); | 603 | put_task_struct(task); |
616 | } | 604 | } |
617 | 605 | ||
618 | if (!ns) | 606 | if (!ns) |
619 | goto err; | 607 | goto err; |
620 | if (!fs) | 608 | if (ret) |
621 | goto err_put_ns; | 609 | goto err_put_ns; |
622 | 610 | ||
623 | read_lock(&fs->lock); | ||
624 | root = fs->root; | ||
625 | path_get(&root); | ||
626 | read_unlock(&fs->lock); | ||
627 | put_fs_struct(fs); | ||
628 | |||
629 | ret = -ENOMEM; | 611 | ret = -ENOMEM; |
630 | p = kmalloc(sizeof(struct proc_mounts), GFP_KERNEL); | 612 | p = kmalloc(sizeof(struct proc_mounts), GFP_KERNEL); |
631 | if (!p) | 613 | if (!p) |