aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-05-17 18:17:33 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-05-17 18:35:02 -0400
commit5ab8271899658042fabc5ae7e6a99066a210bc0e (patch)
tree6961d8faa53d7e2f535d14acc3bb8536a268fb8d
parente4b4e441323b7988a344f88d7ee3f8fcb17db048 (diff)
fs/proc: simplify and clarify get_mm_cmdline() function
We have some very odd semantics for reading the command line through /proc, because we allow people to rewrite their own command line pretty much at will, and things get positively funky when you extend your command line past the point that used to be the end of the command line, and is now in the environment variable area. But our weird semantics doesn't mean that we should write weird and complex code to handle them. So re-write get_mm_cmdline() to be much simpler, and much more explicit about what it is actually doing and why. And avoid the extra check for "is there a NUL character at the end of the command line where I expect one to be", by simply making the NUL character handling be part of the normal "once you hit the end of the command line, stop at the first NUL character" logic. It's quite possible that we should stop the crazy "walk into environment" entirely, but happily it's not really the usual case. NOTE! We tried to really simplify and limit our odd cmdline parsing some time ago, but people complained. See commit c2c0bb44620d ("proc: fix PAGE_SIZE limit of /proc/$PID/cmdline") for details about why we have this complexity. Cc: Tejun Heo <tj@kernel.org> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: Jarod Wilson <jarod@redhat.com> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--fs/proc/base.c186
1 files changed, 65 insertions, 121 deletions
diff --git a/fs/proc/base.c b/fs/proc/base.c
index c4d963a12162..2665bbbb4cca 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -206,24 +206,16 @@ static int proc_root_link(struct dentry *dentry, struct path *path)
206} 206}
207 207
208static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf, 208static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf,
209 size_t _count, loff_t *pos) 209 size_t count, loff_t *ppos)
210{ 210{
211 char *page;
212 unsigned long count = _count;
213 unsigned long arg_start, arg_end, env_start, env_end; 211 unsigned long arg_start, arg_end, env_start, env_end;
214 unsigned long len1, len2, len; 212 unsigned long pos, len;
215 unsigned long p; 213 char *page;
216 char c;
217 ssize_t rv;
218 214
219 /* Check if process spawned far enough to have cmdline. */ 215 /* Check if process spawned far enough to have cmdline. */
220 if (!mm->env_end) 216 if (!mm->env_end)
221 return 0; 217 return 0;
222 218
223 page = (char *)__get_free_page(GFP_KERNEL);
224 if (!page)
225 return -ENOMEM;
226
227 down_read(&mm->mmap_sem); 219 down_read(&mm->mmap_sem);
228 arg_start = mm->arg_start; 220 arg_start = mm->arg_start;
229 arg_end = mm->arg_end; 221 arg_end = mm->arg_end;
@@ -231,126 +223,78 @@ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf,
231 env_end = mm->env_end; 223 env_end = mm->env_end;
232 up_read(&mm->mmap_sem); 224 up_read(&mm->mmap_sem);
233 225
234 BUG_ON(arg_start > arg_end); 226 if (arg_start >= arg_end)
235 BUG_ON(env_start > env_end); 227 return 0;
236
237 len1 = arg_end - arg_start;
238 len2 = env_end - env_start;
239 228
240 /* Empty ARGV. */
241 if (len1 == 0) {
242 rv = 0;
243 goto out_free_page;
244 }
245 /* 229 /*
246 * Inherently racy -- command line shares address space 230 * We have traditionally allowed the user to re-write
247 * with code and data. 231 * the argument strings and overflow the end result
232 * into the environment section. But only do that if
233 * the environment area is contiguous to the arguments.
248 */ 234 */
249 rv = access_remote_vm(mm, arg_end - 1, &c, 1, FOLL_ANON); 235 if (env_start != arg_end || env_start >= env_end)
250 if (rv <= 0) 236 env_start = env_end = arg_end;
251 goto out_free_page;
252
253 rv = 0;
254
255 if (c == '\0') {
256 /* Command line (set of strings) occupies whole ARGV. */
257 if (len1 <= *pos)
258 goto out_free_page;
259
260 p = arg_start + *pos;
261 len = len1 - *pos;
262 while (count > 0 && len > 0) {
263 unsigned int _count;
264 int nr_read;
265
266 _count = min3(count, len, PAGE_SIZE);
267 nr_read = access_remote_vm(mm, p, page, _count, FOLL_ANON);
268 if (nr_read < 0)
269 rv = nr_read;
270 if (nr_read <= 0)
271 goto out_free_page;
272
273 if (copy_to_user(buf, page, nr_read)) {
274 rv = -EFAULT;
275 goto out_free_page;
276 }
277 237
278 p += nr_read; 238 /* We're not going to care if "*ppos" has high bits set */
279 len -= nr_read; 239 pos = arg_start + *ppos;
280 buf += nr_read; 240
281 count -= nr_read; 241 /* .. but we do check the result is in the proper range */
282 rv += nr_read; 242 if (pos < arg_start || pos >= env_end)
283 } 243 return 0;
284 } else { 244
285 /* 245 /* .. and we never go past env_end */
286 * Command line (1 string) occupies ARGV and 246 if (env_end - pos < count)
287 * extends into ENVP. 247 count = env_end - pos;
288 */ 248
289 struct { 249 page = (char *)__get_free_page(GFP_KERNEL);
290 unsigned long p; 250 if (!page)
291 unsigned long len; 251 return -ENOMEM;
292 } cmdline[2] = { 252
293 { .p = arg_start, .len = len1 }, 253 len = 0;
294 { .p = env_start, .len = len2 }, 254 while (count) {
295 }; 255 int got;
296 loff_t pos1 = *pos; 256 size_t size = min_t(size_t, PAGE_SIZE, count);
297 unsigned int i; 257
298 258 got = access_remote_vm(mm, pos, page, size, FOLL_ANON);
299 i = 0; 259 if (got <= 0)
300 while (i < 2 && pos1 >= cmdline[i].len) { 260 break;
301 pos1 -= cmdline[i].len; 261
302 i++; 262 /* Don't walk past a NUL character once you hit arg_end */
263 if (pos + got >= arg_end) {
264 int n = 0;
265
266 /*
267 * If we started before 'arg_end' but ended up
268 * at or after it, we start the NUL character
269 * check at arg_end-1 (where we expect the normal
270 * EOF to be).
271 *
272 * NOTE! This is smaller than 'got', because
273 * pos + got >= arg_end
274 */
275 if (pos < arg_end)
276 n = arg_end - pos - 1;
277
278 /* Cut off at first NUL after 'n' */
279 got = n + strnlen(page+n, got-n);
280 if (!got)
281 break;
303 } 282 }
304 while (i < 2) {
305 p = cmdline[i].p + pos1;
306 len = cmdline[i].len - pos1;
307 while (count > 0 && len > 0) {
308 unsigned int _count, l;
309 int nr_read;
310 bool final;
311
312 _count = min3(count, len, PAGE_SIZE);
313 nr_read = access_remote_vm(mm, p, page, _count, FOLL_ANON);
314 if (nr_read < 0)
315 rv = nr_read;
316 if (nr_read <= 0)
317 goto out_free_page;
318
319 /*
320 * Command line can be shorter than whole ARGV
321 * even if last "marker" byte says it is not.
322 */
323 final = false;
324 l = strnlen(page, nr_read);
325 if (l < nr_read) {
326 nr_read = l;
327 final = true;
328 }
329
330 if (copy_to_user(buf, page, nr_read)) {
331 rv = -EFAULT;
332 goto out_free_page;
333 }
334
335 p += nr_read;
336 len -= nr_read;
337 buf += nr_read;
338 count -= nr_read;
339 rv += nr_read;
340
341 if (final)
342 goto out_free_page;
343 }
344 283
345 /* Only first chunk can be read partially. */ 284 got -= copy_to_user(buf, page, got);
346 pos1 = 0; 285 if (unlikely(!got)) {
347 i++; 286 if (!len)
287 len = -EFAULT;
288 break;
348 } 289 }
290 pos += got;
291 buf += got;
292 len += got;
293 count -= got;
349 } 294 }
350 295
351out_free_page:
352 free_page((unsigned long)page); 296 free_page((unsigned long)page);
353 return rv; 297 return len;
354} 298}
355 299
356static ssize_t get_task_cmdline(struct task_struct *tsk, char __user *buf, 300static ssize_t get_task_cmdline(struct task_struct *tsk, char __user *buf,