One principle of kernel programming is that: do not trust anything users
passed in. Since we assume that users are bad, they will do anything they can
to crash the kernel (just as $OS161_SRC/user/testbin/badcall/badcall.c do). So
we need pay special attention to the arguments of the system calls, especially the
pointers.
$OS161_SRC/kern/vm/copyinout.c provides several useful facilities to safely copy user
level arguments into kernel or vice versa. They assure that even if user arguments is
illegal, the kernel can still get control and handle the error, instead of just
crash. So let's see how can they be applied in the system calls.
User space strings
Some system call, e.g. open, chdir, execv, requires a user level string as
arguments. We can use copyinstr to do this. See the prototype of copyinstr:
int copyinstr(const_userptr_t usersrc, char* dest, size_t len, size_t *actual)
const_userptr_t is just a signpost that make usersrc explicitly looks like a user
pointer. So basically, this function copies a \0 terminated user
space string into kernel buffer dest, and copy as much as len bytes, and
return the actual bytes copied in actual. Note that copyinstr will also
copy the last \ 0. Suppose we have a function that takes a user space string
as argument.
int foo (char* name) {
char kbuf[BUF_SIZE];
int err;
size_t actual;
if ((err = copyinstr((const_userptr_t)name, kbuf, BUF_SIZE, &actual)) != 0)
{
return err;
}
return 0;
}
Then if we call foo("hello"), on success, actual will be 6, including the
last \0.
User space buffer
In system calls like read or write, we need to read from or write to user space
buffers. We can use copyin or copyout here. For example:
int foo_read(unsigned char* ubuf, size_t len)
{
int err;
void* kbuf = kmalloc(len);
if ((err = copyin((const_userptr_t)ubuf, kbuf, len)) != 0)
{
kfree(kbuf);
return err;
}
if ((err = copyout(kbuf, (userptr_t)ubuf, len)) != 0)
{
kfree(kbuf);
return err;
}
return 0;
}