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