xv6, line by line
test yourself

Test yourself · category 15 of 20

User pointers: copyin, copyout, arguments

How the kernel reads system-call arguments from the trapframe and copies user memory in and out by walking the process’s page table in software, page by page, so that a bad pointer becomes -1 instead of a kernel crash.

1warm-upChoose one

cat calls open("README", 0), and in the kernel the saved a0 is 0x3fe0, the address of the string in cat’s memory. Why can’t sys_open simply cast that number to char * and read the name through it?

2warm-upChoose all that apply

A user program passes garbage in every argument register. Which of these functions reject a bad user-supplied value themselves, by returning -1?

3warm-upClick the line

cat’s page table maps the trapframe page and the stack guard page, both without PTE_U. Click the line in walkaddr that stops the kernel from copying into or out of such pages on a user’s behalf.

kernel/vm.c
118// Look up a virtual address, return the physical address,
119// or 0 if not mapped.
120// Can only be used to look up user pages.
127 if (va >= MAXVA)
128 return 0;
131 if (pte == 0)
132 return 0;
133 if ((*pte & PTE_V) == 0)
134 return 0;
135 if ((*pte & PTE_U) == 0)
136 return 0;
138 return pa;

Your pick: none yet (click a line in the code)

4solidType a number

sys_open calls argstr(0, path, MAXPATH) with char path[MAXPATH] and MAXPATH = 128. What is the longest path, in characters not counting the NUL, that this call accepts?

kernel/vm.c
411int
416 int got_null = 0;
418 while (got_null == 0 && max > 0) {
421 if (pa0 == 0) {
422 if ((pa0 = vmfault(pagetable, psz, va0, 1)) == 0) {
423 return -1;
424 }
425 }
426 n = PGSIZE - (srcva - va0);
427 if (n > max)
428 n = max;
430 char *p = (char *)(pa0 + (srcva - va0));
431 while (n > 0) {
432 if (*p == '\0') {
433 *dst = '\0';
435 break;
436 } else {
437 *dst = *p;
438 }
439 --n;
440 --max;
441 p++;
442 dst++;
443 }
446 }
447 if (got_null) {
448 return 0;
449 } else {
450 return -1;
451 }
decimal, 0x hex or 0b binary
5solidChoose all that apply

cat has p->sz = 0x4000, laid out as in Tour 6: System-call arguments and user pointers: code and data at 0x0000-0x1fff, the guard page at 0x2000 (mapped, no PTE_U), and the stack page at 0x3000, which holds "README\0" at 0x3fe0 and "cat\0" at 0x3ff0. For which of these addresses, passed as the open path, does copyinstr return -1?

6deepChoose one

pipewrite calls copyin once per byte while holding pi->lock, a spinlock. Why is that allowed?

kernel/pipe.c
77pipewrite(struct pipe *pi, uint64 addr, int n)
79 int i = 0;
80 struct proc *pr = myproc();
83 while (i < n) {
84 if (pi->readopen == 0 || killed(pr)) {
86 return -1;
87 }
88 if (pi->nwrite == pi->nread + PIPESIZE) { //DOC: pipewrite-full
94 } else {
95 char ch;
96 if (copyin(pr->pagetable, pr->sz, &ch, addr + i, 1) == -1) {
97 if (i == 0)
98 i = -1;
99 break;
100 }
102 i++;
103 }
104 }
108 return i;
7solidTrue or false, and why

True or false: a user program can make read(fd, (char *)main, 100) overwrite its own code with file data, because the code page has PTE_U and lies below p->sz.

Why?

8solidPut in order

sys_open fetches its path with argstr(0, path, MAXPATH); the user passed 0x3fe0. Put these steps in the order they happen.

  1. fetchstr returns strlen(path), which is 6
  2. copyinstr rounds the address down to its page, va0 = 0x3000
  3. bytes are copied from the physical page until the NUL
  4. walkaddr checks the leaf PTE for PTE_V and PTE_U and returns the physical page
  5. argaddr copies the saved a0 (0x3fe0) out of the trapframe
  6. fetchstr calls copyinstr with p->pagetable and p->sz
9warm-upChoose one

argraw returns p->trapframe->a0 and so on. Why does it read the trapframe instead of the hart’s real a0-a5 registers?

kernel/syscall.c
34static uint64
35argraw(int n)
37 struct proc *p = myproc();
38 switch (n) {
39 case 0:
40 return p->trapframe->a0;
41 case 1:
42 return p->trapframe->a1;
43 case 2:
44 return p->trapframe->a2;
45 case 3:
46 return p->trapframe->a3;
47 case 4:
48 return p->trapframe->a4;
49 case 5:
50 return p->trapframe->a5;
51 }
52 panic("argraw");
53 return -1;
10solidFill in the machine state

cat is in open, and its hart is inside the byte-copying loop of copyinstr (no lazy page is involved). What is the state of that hart?

kernel/vm.c
426 n = PGSIZE - (srcva - va0);
427 if (n > max)
428 n = max;
430 char *p = (char *)(pa0 + (srcva - va0));
431 while (n > 0) {
432 if (*p == '\0') {
433 *dst = '\0';
435 break;
436 } else {
437 *dst = *p;
438 }
439 --n;
440 --max;
441 p++;
442 dst++;
443 }
11deepFill in the machine state

A write system call on a pipe reaches pipewrite, which holds pi->lock and calls copyin on a never-touched lazy sbrk page. copyin calls vmfault, which calls kalloc. What is the hart’s state at the moment kalloc holds kmem.lock?

12warm-upType a number

copyin is called with srcva = 0x1ff0 and len = 48. How many bytes does the first iteration of its while loop copy?

kernel/vm.c
382int
387 while (len > 0) {
390 if (pa0 == 0) {
391 if ((pa0 = vmfault(pagetable, psz, va0, 1)) == 0) {
392 return -1;
393 }
394 }
395 n = PGSIZE - (srcva - va0);
396 if (n > len)
397 n = len;
398 memmove(dst, (void *)(pa0 + (srcva - va0)), n);
400 len -= n;
401 dst += n;
403 }
404 return 0;
decimal, 0x hex or 0b binary
13solidType a number

sys_exec collects the user’s argv into char *argv[MAXARG], with MAXARG = 32. What is the largest number of argument strings (not counting the terminating 0 pointer) that exec can accept?

kernel/sysfile.c
462 int i;
466 if (argstr(0, path, MAXPATH) < 0) {
467 return -1;
468 }
469 memset(argv, 0, sizeof(argv));
470 for (i = 0;; i++) {
471 if (i >= NELEM(argv)) {
472 goto bad;
473 }
474 if (fetchaddr(uargv + sizeof(uint64) * i, (uint64 *)&uarg) < 0) {
475 goto bad;
476 }
477 if (uarg == 0) {
478 argv[i] = 0;
479 break;
480 }
482 if (argv[i] == 0)
483 goto bad;
484 if (fetchstr(uarg, argv[i], PGSIZE) < 0)
485 goto bad;
486 }
decimal, 0x hex or 0b binary
14warm-upMatch the pairs

Match each function with what it does.

15deepChoose one

lazy_copyinstr grows the heap lazily by two pages with sbrklazy(2 * PGSIZE), writes '/' to the last byte of the first page (p[4095]), and never touches the second page. Then it calls open(&p[4095], O_RDONLY). The string’s NUL would be at p[4096], on the untouched page. What happens?

user/usertests.c
2743void
2746 char *p = sbrk(0);
2749 p = sbrk(0);
2750 if ((uint64)p % PGSIZE != 0) {
2751 printf("%s: sbrk did not align\n", s);
2756 p[4095] = '/';
2757 int fd = open(&p[4095], O_RDONLY);
2758 if (fd < 0) {
2759 printf("could not open /");
16deepChoose one

fetchaddr tests addr >= p->sz || addr + sizeof(uint64) > p->sz. With p->sz = 0x4000, which addr would pass the second test, even though it is outside the process, so that only the first test rejects it?

kernel/syscall.c
10// Fetch the uint64 at addr from the current process.
11int
14 struct proc *p = myproc();
15 if (addr >= p->sz ||
16 addr + sizeof(uint64) > p->sz) // both tests needed, in case of overflow
17 return -1;
18 if (copyin(p->pagetable, p->sz, (char *)ip, addr, sizeof(*ip)) != 0)
19 return -1;
20 return 0;
17solidClick the line

A program passes copyout a buffer in heap memory it grew with lazy sbrk and has never touched. Click the line that makes the copy succeed anyway.

kernel/vm.c
341// Copy from kernel to user.
342// Copy len bytes from src to virtual address dstva in a given page table.
343// Return 0 on success, -1 on error.
344int
350 while (len > 0) {
352 if (va0 >= MAXVA)
353 return -1;
356 if (pa0 == 0) {
357 if ((pa0 = vmfault(pagetable, psz, va0, 0)) == 0) {
358 return -1;
359 }
360 }
363 // forbid copyout over read-only user text pages.
364 if ((*pte & PTE_W) == 0)
365 return -1;
367 n = PGSIZE - (dstva - va0);
368 if (n > len)
369 n = len;
370 memmove((void *)(pa0 + (dstva - va0)), src, n);
372 len -= n;
373 src += n;
375 }
376 return 0;

Your pick: none yet (click a line in the code)

18deepChoose all that apply

A program calls read(fd, buf, 2048) on a regular file at offset 0 whose size is at least 2048. buf is exactly 1024 bytes below p->sz (which is page-aligned), on a page the program has already used. Which statements are true?

kernel/fs.c
509int
513 struct buf *bp;
515 if (off > ip->size || off + n < off)
516 return 0;
517 if (off + n > ip->size)
518 n = ip->size - off;
520 for (tot = 0; tot < n; tot += m, off += m, dst += m) {
522 if (addr == 0)
523 break;
525 m = min(n - tot, BSIZE - off % BSIZE);
526 if (either_copyout(user_dst, dst, bp->data + (off % BSIZE), m) == -1) {
528 tot = -1;
529 break;
530 }
532 }
533 return tot;
19solidTrue or false, and why

True or false: walkaddr's test if (va >= MAXVA) return 0; is redundant, because vmfault would refuse such an address anyway (it is above p->sz).

kernel/vm.c
118// Look up a virtual address, return the physical address,
119// or 0 if not mapped.
120// Can only be used to look up user pages.
127 if (va >= MAXVA)
128 return 0;
131 if (pte == 0)
132 return 0;
133 if ((*pte & PTE_V) == 0)
134 return 0;
135 if ((*pte & PTE_U) == 0)
136 return 0;
138 return pa;

Why?

20warm-upChoose one

When copyin translates a user address, which page table does it walk?

21warm-upChoose one

walkaddr returns a physical address pa0, and copyin then calls memmove(dst, (void *)(pa0 + (srcva - va0)), n) while the hart uses the kernel page table. Why does using a physical address as a pointer work?

kernel/vm.c
382int
387 while (len > 0) {
390 if (pa0 == 0) {
391 if ((pa0 = vmfault(pagetable, psz, va0, 1)) == 0) {
392 return -1;
393 }
394 }
395 n = PGSIZE - (srcva - va0);
396 if (n > len)
397 n = len;
398 memmove(dst, (void *)(pa0 + (srcva - va0)), n);
400 len -= n;
401 dst += n;
403 }
404 return 0;
22solidChoose one

A buggy program passes 0x2f00, inside its stack guard page, as the buffer for fstat. In copyout, walkaddr returns 0, so vmfault gets its chance. The address is below p->sz. Why doesn’t vmfault give the program a fresh page there?

kernel/vm.c
454// allocate and map user memory if process is referencing a page
455// that was lazily allocated in sys_sbrk().
456// returns 0 if va is invalid or already mapped, or if
457// out of physical memory, and physical address if successful.
463 if (va >= psz)
464 return 0;
467 return 0;
468 }
470 if (mem == 0)
471 return 0;
472 memset((void *)mem, 0, PGSIZE);
474 kfree((void *)mem);
475 return 0;
476 }
477 return mem;
480int
484 if (pte == 0) {
485 return 0;
486 }
487 if (*pte & PTE_V) {
488 return 1;
489 }
490 return 0;
23solidDecode the bits

In one run, gdb shows the leaf PTE for cat’s guard page at 0x2000 as 0x21fc7407. Decode it.

Value: 0x21fc7407

24solidChoose one

sys_open copies its path into char path[MAXPATH] on the kernel stack, but sys_exec copies each argument string into a whole page from kalloc. Why the difference?

kernel/sysfile.c
462 int i;
466 if (argstr(0, path, MAXPATH) < 0) {
467 return -1;
468 }
469 memset(argv, 0, sizeof(argv));
470 for (i = 0;; i++) {
471 if (i >= NELEM(argv)) {
472 goto bad;
473 }
474 if (fetchaddr(uargv + sizeof(uint64) * i, (uint64 *)&uarg) < 0) {
475 goto bad;
476 }
477 if (uarg == 0) {
478 argv[i] = 0;
479 break;
480 }
482 if (argv[i] == 0)
483 goto bad;
484 if (fetchstr(uarg, argv[i], PGSIZE) < 0)
485 goto bad;
486 }
25deepChoose one

A program whose file descriptor 1 is the console calls write(1, (char *)0x80000000, 8). What does write return?

kernel/console.c
62int
65 char buf[32]; // move batches from user space to uart.
66 int i = 0;
68 while (i < n) {
69 int nn = sizeof(buf);
70 if (nn > n - i)
71 nn = n - i;
72 if (either_copyin(buf, user_src, src + i, nn) == -1)
73 break;
75 i += nn;
76 }
78 return i;