n0v (n0v) wrote,
n0v
n0v

Writing FreeBSD 5.x syscall modules

Note: this is my first article in English, it's written just for fun


Basics

First of all, every kernel module must have a special function, usually called load(). It should looks something like this:

static int
load (struct module *module, int cmd, void *arg)
{
int error = 0;

switch (cmd) {
case MOD_LOAD:
/*
* This code will be executed when our module will be loaded
*/
printf("module was loaded!\n");
break;
case MOD_UNLOAD:
/*
* And this one'll be executed when our module will be unloaded
*/
printf("module was unloaded!\n");
break;
default:
error = EINVAL;
break;
}

return error;
}

So, I think it' pretty clean for this moment.



Syscalls stuff


As usual, one writes syscall modules to replace (modify) some system syscall (adding the new one is also pretty simple, thougt).

FreeBSD keeps it's syscall list in a struct called sysent. You can find it's defenition here:
/usr/src/sys/kern/init_sysent.c. So, if we want to replace some sytem call, we just need to modify the sysent struct. We need to add something like "sysent[SYS_somesyscall].sy_call = (sy_call_t *)my_new_syscall;" after the MOD_LOAD. If you don't want to have troubles when your module 'll b unloaded, you need to change everything back, i.e, put line "sysent[SYS_somesyscall].sy_call = (sy_call_t *)somesyscall;" to the MOD_UNLOAD section.

It's time to write your new syscall code. Let's modify the execve(2) syscall for example in a such way that it will not be allowed to run apps from the /tmp folder.

static int
new_execve(struct thread *td, register struct execve_args *uap)
{
if (strncmp(uap->fname, "/tmp/", 5) == 0) {
printf("execve(2): %s by pid %d (%s), user: %d\n", uap->fname, td->td_proc->p_pid,
td->td_proc->p_comm, td->td_ucred->cr_uid);
printf("execve(2): dissallowed for /tmp\n");
return EACCES;
}

return (execve(td, uap));
}

Let's see what's going on here: we take the filename of app to be executed and if it's first 5 chars matches with "/tmp/" we display warning about it and returns EACCESS (Actually, "Permission denied"). That's pretty simple. isn't it?

All we need now is to create sysent struct for our module:

static struct sysent new_execve_sysent = {
AS(execve_args),
(sy_call_t *)new_execve
};

Here is a complite example:

#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysproto.h>
#include <sys/syscall.h>
#include <sys/ucred.h>

static int new_execve(struct thread *td, register struct execve_args *);

static int offset = NO_SYSCALL;

static int
new_execve(struct thread *td, register struct execve_args *uap)
{
if (strncmp(uap->fname, "/tmp/", 5) == 0) {
printf("execve(2): %s by pid %d (%s), user: %d\n", uap->fname, td->td_proc->p_pid,
td->td_proc->p_comm, td->td_ucred->cr_uid);
printf("execve(2): dissallowed for /tmp\n");
return EACCES;
}

return (execve(td, uap));
}

#define AS(name) (sizeof(struct name) / sizeof(register_t))

static struct sysent new_execve_sysent = {
AS(execve_args),
(sy_call_t *)new_execve
};

static int
load (struct module *module, int cmd, void *arg)
{
int error = 0;

switch (cmd) {
case MOD_LOAD:
printf("execve() syscall was replaced\n");
sysent[SYS_execve] = new_execve_sysent;
break;
case MOD_UNLOAD:
printf("execve() syscall was restored\n");
sysent[SYS_execve].sy_call = (sy_call_t *)execve;
break;
default:
error = EINVAL;
break;

}

return error;
}

SYSCALL_MODULE(syscall_execve, &offset, &new_execve_sysent, load, NULL);

Compiling
It is a makefiles I love FreeBSD for. :)
To compile our module you just need to create a such Makefile:

KMOD=my_execve
SRCS=execve.c

.include <bsd.kmod.mk>

That's all. You just need to run "make" now. To load your module, use kldload ./my_execve.ko, to unload: kldunload my_execve.ko.

That's all, have a nice day.

  • Post a new comment

    Error

    default userpic
  • 1 comment