3.4 Tasks
3.4 Questions
3.9 Questions
4.2 Tasks
4.3 Questions
- The makefile has a variable which specifies the kernel directory:
KDIR := /lib/modules/$(shell uname -r)/build
. On the virtual image, this variabla becomes /lib/modules/2.6.27.7-smp/build. - Kernel modules do not have access to standard C libraries. We have to use libraries provided by the kernel. printk() is a function defined in the kernel which is almost eqvuivalent to printf().
- The -C flag in the makefile tells make to change to the specified directory before doing anything else. I navigated to /lib/modules/2.6.27.7-smp/build and discovered another Makefile, which performs the actual build. I scrolled through the file and noticed some code which handles both
modules
andclean
. I didn't quite understand what the -M flag is doing though. I wasn't able to find it in the man pages either. - If we remove MODULE_LICENSE from our code, the kernel will complain when we are loading the module:
modpost: missing MODULE_LICENSE()
.
a) & c)
package main
import "os"
import "fmt"
import "log"
func main() {
file, err := os.Open("/dev/simp_read")
if err != nil {
log.Fatal(err)
}
data := make([]byte, 100)
count, err := file.Read(data)
if err != nil {
log.Fatal(err)
}
//The minus 1 is to get rid of the trailing \n
fmt.Printf("%q\n", data[:(count-1)])
}
b)
static bool msgWritten;
// open function - called when the "file" /dev/simp_read is opened in userspace
static int dev_open (struct inode *inode, struct file *file)
{
counter++;
printk("simplkm_read: skeleton device driver open\n");
return 0;
}
// read function called when from /dev/simp_read is read
static ssize_t dev_read (struct file *file, char *buf, size_t count, loff_t *ppos)
{
if (msgWritten) {
return 0;
}
msgWritten = true;
char theBuf[50];
int bufLen=0;
sprintf(theBuf, "Number of driver read=%d\n", counter );
bufLen = strlen( theBuf);
printk("simplkm_read: %s, buflen=%d\n",theBuf, bufLen );
if( copy_to_user( buf, theBuf, bufLen ) )
{
printk("simplkm_read: copy_to_user failed\n" );
return -EFAULT;
}
else
return bufLen;
}
a)
static ssize_t dev_write (struct file *file, const char *buf, size_t count, loff_t *ppos)
{
sprintf(msg, "");
int bufLen=0;
if( copy_from_user( msg, buf, count ) )
{
printk("simplkm_rw: copy_from_user failed\n");
return -EFAULT;
}
else
{
printk(KERN_INFO "%s\n", msg);
sprintf(msg + strlen(msg), "\n");
bufLen = strlen( msg );
return bufLen;
}
}
And add this line to the file_operations
struct:
.write = dev_write
c)
package main
import (
"flag"
"fmt"
"log"
"os"
)
func main() {
msg := flag.String("write", "", "message to write to /dev/simp_rw")
flag.Parse()
if *msg != "" {
write(*msg)
} else {
read()
}
}
func write(msg string) {
file, err := os.OpenFile("/dev/simp_rw", os.O_RDWR, 0644)
defer file.Close()
checkError(err)
_, err = file.Write([]byte(msg))
checkError(err)
}
func read() {
file, err := os.Open("/dev/simp_rw")
defer file.Close()
checkError(err)
data := make([]byte, 100)
count, err := file.Read(data)
checkError(err)
fmt.Printf("%q\n", data[:(count-1)])
}
func checkError(err error) {
if err != nil {
log.Fatal(err)
}
}
d) I believe this question is more relevant for task b. Task a only handles one message, so we don't have to think about protection.
The buffer is cleared every time write()
is called, so we will never have a buffer overflow.
a)
Where memory is allocated | Tried to dereference | Textual value of pointer | Result |
---|---|---|---|
Kernel | Userspace | cd448180 | Segmentation fault |
Userspace | Kernel | 0x804a170 | Successfully reads the data |
Userspace process 1 | Userspace process 2 | 0x804a008 | Segmentation fault |
Userspace thread 1 | Userspace thread 2 | 0x804a008 | Successfully reads the data |
Kernel driver 1 | Kernel driver 2 | cd19e500 | Successfully reads the data |
b)
Why do we have to use copy_to_user()
and copy_from_user()
in a kernel driver? Protection is a keyword here. We should not be able to acecss kernel addresses directly from user space.
The functions check this by calling access_ok()
on the adress. The functions handles errors as well. This means that if we encounter a page fault during copy,
we can return -EFAULT
to the user and prevent the kernel from crashing.