Programación de LKMs en Solaris

Como últimamente estoy muy liado (tengo que preparar e impartir clases en la facultad y en el master de seguridad), voy a permitirme rescatar código que tenía por aqui olvidado para no dejar el blog abandonado. Lo que voy a contar no es nada nuevo, pero no hay tampoco mucha documentacion en castellano y quiza a alguien le venga bien. En este texto aprenderemos a programar modulos del nucleo para Solaris y en el siguiente articulo veremos un ejemplo practico. El codigo esta sin tocar desde hace anos, asi que puede que haya algun bug, no estaria de mas que lo comentarais si asi lo veis 😉

 

LKMs (Loadable Kernel Modules)

¿Qué es un LKM?

Un LKM o Loadable Kernel Module es una funcionalidad modular dentro del kernel del Sistema Operativo que puede ser cargada o descargada bajo demanda y permite una mejor utilización de la memoria destinada al kernel dentro de la memoria del sistema.

Como ya sabemos, el kernel de UNIX comenzó siendo un gran ejecutable indivisible, un kernel monolítico. En él estaban incluidas todas las funcionalidades del sistema. Para entornos con altos requerimientos en cuanto a velocidad de sus peticiones al sistema y poca variación de sus características (por ejemplo, sistemas embebidos) esta solución era la mejor, pero los sistemas de propósito general pronto notaron sus carencias. La memoria es un recurso muy preciado y muchas partes del kernel tenían que estar en memoria a pesar de su escaso uso (drivers para dispositivos poco usados, etc.). Resultaba obvio que era necesario modularizar el kernel para hacer un uso más eficiente de la memoria y permitir mayor flexibilidad.

El kernel se dividió en multitud de “piezas” y sólo las piezas que utilizamos en un momento concreto serán las que estén cargadas en memoria. Para ello necesitaremos un gestor de carga y descarga de módulos del kernel, como ya veremos más adelante

¿Para qué se utilizan?

Los LKMs se utilizan principalmente en dos campos:

  • Drivers de dispositivos
  • Seguridad (IDS, troyanos, etc.)

Tal y como hemos comentado con anterioridad, un sistema con muchos dispositivos diferentes necesita gestionar un gran número de drivers para acceder a ellos, el gasto de memoria sería muy elevado si todos ellos estuvieran dentro de un kernel monolítico. En lugar de esto, mediante LKMs se cargan solamente los que se están utilizando en un preciso momento, y son descargados cuando dejan de utilizarse. Construir un módulo para el manejo de un dispositivo es una tarea relativamente compleja, pero Solaris pretende ayudar a sus usuarios desarrolladores mediante la interfaz DDI/DKI (Device Driver Interface/Driver Kernel Interface).

Su implicación en el campo de la seguridad también es bastante clara: los LKMs son una puerta a la memoria del kernel, por lo que un uso malicioso de ellos podría provocar comportamientos no deseados del sistema. Sin el cuidado adecuado se podría introducir código arbitrario en el kernel, con las consecuencias que ello conllevaría. La diferencia entre la ejecución de código arbitrario como usuario y la introducción de éste en el kernel es abismal: el kernel del sistema actúa como guardián del mismo y decide qué cosas pueden hacerse o no. Si ejecutamos código arbitrario como usuario, aunque sea como superusuario (root), siempre estaremos supeditados a las restricciones propias del kernel, sin embargo, si modificamos el kernel podremos superar esas restricciones, y el único límite entonces estará en los aspectos físicos del sistema. Si permitimos que un atacante modifique el kernel, estaremos dando la posibilidad de fijar las reglas de juego, ocultarse a todos los efectos a todos los usuarios (“root” incluido, como ya hemos dicho) y elaborar un mecanismo para que no sea posible su expulsión del sistema.

¿Cómo se utilizan?

Para utilizar un LKM es necesario cargarlo dentro de la memoria del kernel. Esto no se realiza de forma convencional, existen comandos especiales para la carga y descarga de los módulos del kernel. La mayoría de las funcionalidades del kernel de Solaris también utilizan este método (TCP/IP, SCSI, UFS…), así como herramientas o dispositivos externos (ipf, pppd, drivers de tarjetas de red…), por lo que no debe extrañarnos que exista un procedimiento bien definido para ello.

El proceso de carga y descarga de los módulos lo gestionan dos comandos muy sencillos de utilizar, “/usr/sbin/modload” y “/usr/sbin/modunload”, respectivamente. Antes de cargar un módulo conviene informarnos acerca del resto de módulos cargados, sus características, tamaño y demás, por lo que haremos uso del comando “/usr/sbin/modinfo”. Todos estos comandos tienen sus páginas de manual (“man modinfo”, por ejemplo), por lo que no nos extenderemos en detallar su empleo.

Veamos un ejemplo del proceso:

bash-2.03# cc -g -D_KERNEL -DSVR4 -DSOL2 -O2 -c quark.c
bash-2.03# ld –o quark -r quark.o

bash-2.03# modinfo
Id Loadaddr   Size Info Rev Module Name
 5 fe8ec000   389a   1   1  specfs (filesystem for specfs)
 7 fe8f0c16   2334   1   1  TS (time sharing sched class)
 8 fe8f2ada    886   -   1  TS_DPTBL (Time sharing dispatch table)
10 fe8f2b52    194   -   1  pci_autoconfig (PCI BIOS interface)
11 fe8f2c66  20d5c   2   1  ufs (filesystem for ufs)
12 fe911e4e    164   -   1  fssnap_if (File System Snapshot Interface)

[...]

132 fea68de1    1bc  21   1  redirmod (redirection module)
133 fe9ffdfd    d58  22   1  bufmod (streams buffer mod)
134 e12ad000   9914  13   1  pcfs (filesystem for PC)

bash-2.03# modload quark

bash-2.03# modinfo
Id Loadaddr   Size Info Rev Module Name
 5 fe8ec000   389a   1   1  specfs (filesystem for specfs)
 7 fe8f0c16   2334   1   1  TS (time sharing sched class)
 8 fe8f2ada    886   -   1  TS_DPTBL (Time sharing dispatch table)
10 fe8f2b52    194   -   1  pci_autoconfig (PCI BIOS interface)
11 fe8f2c66  20d5c   2   1  ufs (filesystem for ufs)

[...]

132 fea68de1    1bc  21   1  redirmod (redirection module)
133 fe9ffdfd    d58  22   1  bufmod (streams buffer mod)
134 e12ad000   9914  13   1  pcfs (filesystem for PC)
135 e106fe56    1bf   -   1  quark (Quark LKM)

Tal y como hemos visto, cada módulo dispone de un identificativo único (Id), con el que nos referiremos a él en el proceso de descarga, por lo que es importante recordarlo. modinfo, además, nos informa de la dirección de memoria a partir de la cual se ha cargado el módulo, su tamaño en bytes (la suma de sus secciones .text, .data y .bss), el número de información del driver (Info), el número de revisión del módulo y su nombre o descripción. El número de información del driver está en función del tipo de módulo que sea: si se trata de un driver de dispositivo “Info” contendrá un número informativo, pero si el módulo no es un driver, no incluirá este número. Este último tipo de módulos se conocen como misceláneos (“misc”) y es necesario declararlos como tales.

Funcionamiento de un LKM

Una vez cargado, un LKM reside en el área de memoria reservada para el kernel, y es capaz de acceder a las estructuras de datos creadas para la gestión de las llamadas al sistema. En función del uso que quiera darse a un módulo en concreto, éste deberá fijarse en unas u otras estructuras de datos e interrupciones, para poder dispensar el servicio deseado. Así pues, un módulo que haga funciones de driver de dispositivo, deberá estar atento a las peticiones de uso del mismo, a los flujos de datos entre las aplicaciones y el kernel, que es el único que maneja realmente el dispositivo, etc.

Un LKM orientado a la seguridad del sistema funcionará de forma similar, pero sus objetivos serán diferentes. Habrá que fijarse muy de cerca en la tabla de syscalls (como explicaremos posteriormente), los intentos de carga de otros módulos, las peticiones extrañas al sistema por parte de los usuarios, y todas los aspectos importantes en cuanto a registro del comportamiento normal del sistema.

Programación de un LKM

Estructura básica de un módulo de kernel

Los módulos de kernel necesitan disponer de unas estructuras básicas para poder ser cargados dentro del kernel. En Solaris esto es especialmente cierto y deberemos definir muchas variables y estructuras para poder programar un LKM sencillo. Otros Sistemas Operativos como Linux o FreeBSD no precisan una estructura básica tan compleja y pueden crearse módulos para ellos de forma muy sencilla.

Lo primero que deberemos incluir serán las cabeceras para utilizar el interfaz DDI de Solaris. Esto es necesario siempre, aunque no vayamos a desarrollar un driver de dispositivo. Otra cosa que deberemos incluir siempre en nuestros módulos será el conjunto de estructuras necesarias para que los comandos de carga, descarga e información sobre el módulo funcionen correctamente (struct como mod_ops, mod_miscops, modlmisc ó modlinkage).

Veamos un código de ejemplo de la estructuras básicas que han de existir en un LKM para Solaris:

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>

extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {
    &mod_miscops,
    "Nombre del LKM",
};

static struct modlinkage modlinkage = {
    MODREV_1,
    (void *)&modlmisc,
    NULL
};

Como podemos observar, hemos incluido las cabeceras necesarias y hemos definido las estructuras modlmisc y modlinkage para un módulo “misceláneo”, es decir, para un módulo que no va a servir como driver de dispositivo. Esto implica que no dispone de información sobre el número de información del driver (Info). En la estructura modlmisc definimos el nombre que el módulo devolverá al sistema cuando sea solicitado (“Nombre del LKM”).

Existe mucha ayuda en la documentación de Solaris acerca de estas estructuras, sus datos y su manejo (“man modldrv”, “man modlinkage”, “man modlstrmod”), pero considero que quedan fuera del ámbito de este texto y con saber la estructura básica de un LKM en Solaris nos basta.

Además de estas estructuras, todo módulo de kernel en Solaris debe incluir como mínimo tres funciones: _init(), _fini() e _info(). Su cometido es bastante obvio a la vista de sus nombres:

_init() inicializa el módulo y lo prepara para su carga. Esta función se llama antes de realizar cualquier otra cosa en un LKM.

int _init(void)
{
    int i;
    if  != 0)
        cmn_err(CE_NOTE,"No se pudo instalar el modulo quark\n");
    else
        cmn_err(CE_NOTE,"quark: instalación correcta");
    return i;
}

Dentro de _init() se llama a mod_install() que toma modlinkage como argumento e instala en función de esos valores el módulo en el sistema. _init() devuelve el valor de su llamada a mod_install(), es decir, informa si se ha cargado correctamente el módulo o no. Otro punto importante es el uso de la función cmn_err(), que mediante el parámetro CE_NOTE indica que los mensajes se introducirán en el log del sistema como una notificación.

_info() devuelve información acerca de un módulo. Siempre que llamemos a esta función, habrá que llamar a mod_info() que utilizará los datos almacenados en la estructura modinfo. Si modinfo tiene como valor del nombre de módulo un string vacío, mod_info()no devolverá nada ( y /usr/sbin/modinfo no mostrará el módulo en su lista).

int _info(struct modinfo *modinfop)
{
    return (mod_info(&modlinkage, modinfop));
}

_fini() prepara el módulo para su descarga. Cuando queremos descargar un módulo, llamamos a esta función. _fini()a su vez llamara a mod_remove() para proceder a la descarga del módulo. Es importante capturar el valor devuelto por esta función para comprobar si se han producido errores en la descarga.

int _fini(void)
{
    int i;
    if  != 0)
        cmn_err(CE_NOTE,"No se pudo desinstalar el modulo quark\n");
    else
        cmn_err(CE_NOTE,"quark: desinstalación correcta");
    return i;
}

En definitiva, utilizando las estructuras comentadas y las funciones _init(), _fini() e _info() podremos crear nuestros LKMs de forma sencilla, incluyendo lo que consideremos necesario tras _init(), y utilizando los mecanismos estándar de descarga.

Métodos de ocultación del LKM

Si vamos a emplear nuestros módulos para gestionar dispositivos externos no tiene ningún sentido ocultarlos. Es más, al contrario, es conveniente que estén visibles para que los usuarios sean capaces de saber qué módulos correspondientes a drivers de dispositivos están funcionando. Si nuestro objetivo es aumentar la seguridad del sistema, una práctica conveniente es ocultar el propio módulo de protección para no alertar a posibles intrusos y no centrar sus ataques en desactivar el módulo de seguridad. Es, por tanto, importante saber cómo ocultar nuestros módulos para mantener su propia seguridad y, por ende, la de todo el sistema.

Existen varias formas de ocultar un LKM de cara al usuario. La primera de ellas ya la hemos visto en el apartado anterior: si utilizamos una cadena vacía como descripción o nombre de nuestro módulo, modinfo no lo mostrará en su lista (a pesar de continuar cargado dentro del kernel). Además, modunload no devolverá un error si alguien pretende descargar un módulo que no estaba previamente cargado, por lo que no podrán detectar su presencia intentando descargar módulos que no salgan en la lista. Como primera aproximación esta medida puede valer, pero después de un análisis serio, cualquiera con dos dedos de frente podrá adivinar que nuestro módulo está ahí.

Si realmente queremos una protección seria para nuestro módulo que evite que sea listado y descargado de la memoria del kernel, deberemos parchear o suplantar el módulo ksyms que lista y maneja todos los símbolos del kernel en Solaris. Existe documentación al respecto y pruebas de concepto en Sistemas Operativos como Linux y FreeBSD acerca de esto (ver 7. Referencias).

La más reseñable de estas pruebas de concepto puede que sea el módulo itf.c, programado por Plaguez y publicado en la Phrack número 52. Una vez instalado dentro del kernel modificará la estructura mp que lo identifica como módulo y la tabla get_kernel_symbols, evitando de esta manera aparecer tanto en /proc/modules como en la salida de ksyms. Como efecto colateral, el módulo será imposible de descargar. Esto puede verse tanto como una funcionalidad como un defecto.

Captura y Suplantación de las llamadas al sistema

Lo más importante dentro de un módulo de monitorización del sistema es supervisar los eventos que en él se producen para comprobar si todo está dentro de la normalidad. Es necesario por tanto capturar las llamadas al sistema por parte de los diferentes usuarios y permitir realizar comprobaciones de esas llamadas suplantando los servicios originales del kernel.

Ya hemos tratado anteriormente el funcionamiento del kernel en Solaris en cuanto a las syscalls o peticiones al sistema. Todas esas llamadas se almacenan en un array denominado sysent en donde cada una de sus posiciones es una estructura que contiene información acerca de una syscall. Esto funciona de manera similar a otros Sistemas UNIX como Linux o la familia *BSD, si bien cada una de las estructuras para almacenar información acerca de una syscall es diferente en cada sistema.

Es necesario tener esto en cuenta a la hora de portar un LKM de Linux a Solaris, por ejemplo. Además, Solaris trabaja con arquitecturas de 32 y 64 bits mezcladas dentro del sistema, por lo que muchas de las syscalls más comunes (como open o create) tienen sus variedades de 32 y 64 bits. Para conocer cuál de ellas utiliza un determinado programa o comando podemos hacer uso del comando /usr/bin/truss. Así pues, por ejemplo, ps utiliza open() para abrir los ficheros que componen los procesos dentro de /proc, mientras que cat utiliza open64() para mostrar los contenidos de un fichero.

Existen diferentes maneras de suplantar las llamadas o peticiones al sistema de forma limpia y eficaz, pero lo primero que deberemos saber es cómo poder utilizar syscalls que no hayan sido definidas dentro de nuestros módulos. Plaguez da una solución en su artículo del e-zine Phrack: utilizar una macro para poder hacer uso de la programación mediante peticiones al sistema tradicional de la zona de usuario (en la siguiente sección discutiremos la diferencia entre zona de usuario y zona kernel):

/*we need brk systemcall*/
static inline _syscall1(int, brk, void *, end_data_segment);

Mediante este sistema podremos utilizar cualquier syscall de las que se piden normalmente desde la zona de usuario (fork, brk, open, read, write…), es decir, podremos construir la macro exacta para una función concreta de la zona de usuario (nótese que no toda función de la zona de usuario es una petición al sistema, pero sí un compendio de ellas).

Aunque esto pueda parecer un truco, realmente es lo que se utiliza en muchos sistemas para usar syscalls dentro del kernel. Sirva como muestra el siguiente código extraído del código fuente de Linux (/asm/unistd.h):

#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
asm volatile ("int $0x80" \
	: "=a" (__res) \
	: "0" (__NR_##name),"b" ); \
if (__res >= 0) \
	return (type) __res; \
errno = -__res; \
return -1; \
}

NOTA: Las “\”s indican que todo debería estar en una única línea, no son parte del código.

A pesar de lo complejo que pueda parecer este código, tiene una sencilla explicación: simplemente llama a la interrupción 80h (la utilizada en Linux para hacer una petición al sistema) con los argumentos pasados como parámetros: type se utiliza para hacer un cast del valor devuelto, name para llamar a la syscall que corresponda (__NR_name, definidas todas en /asm/unistd.h) y arg1 es el parámetro pasado a la syscall.

Si bien éste es el método utilizado por los programadores del kernel de Linux, existe otra manera un poco más clara de realizar esto y será la que empleemos en nuestros módulos de aquí en adelante. El proceso consiste en definir un prototipo de función que se ajuste a la llamada a la syscall, y luego tomar de la tabla sys_ent el valor para la función definida en el prototipo, por ejemplo:

int (*open)(char *, int, int);    /* declarar el prototipo */
open = sys_call_table[__NR_open]; /* tomar su valor de sysent */

Pragmatic de THC y SVAT usan este método y ha sido probado muchas veces con éxito. En líneas generales, cuando queramos suplantar una llamada o petición al sistema deberemos seguir los siguientes pasos:

  1. Encontrar y estudiar la llamada o petición al sistema que queramos suplantar (conviene fijarse bien en los ficheros de cabecera .h).
  2. Definir un prototipo de función similar a la syscall que queramos utilizar.
  3. Encontrar la syscall original en la tabla de syscalls (sysent[]) y definir con su valor el prototipo previamente creado.
  4. Crear una nueva función que suplante a la syscall original (normalmente hará uso de la syscall suplantada, por lo que los pasos anteriores son muy útiles para no “machacar” la syscall original).
  5. Definir la posición que corresponda dentro de la tabla de syscalls como un puntero a la nueva función que suplantará a la original.

De esta manera, cada vez que el sistema pida la ejecución de esa syscall, se ejecutará la función que nosotros hemos introducido y no la syscall original. Esto suele emplearse para realizar determinados controles sobre la petición y posteriormente llamar a la función que originariamente realizaba la solicitud de esa syscall para que haga su trabajo normalmente. De forma gráfica el proceso sería tal y como lo muestra la siguiente figura:

solaris01.gif

Proceso de suplantación de una llamada al sistema.

Analicemos ahora mediante un ejemplo en código cómo se programarían todos estos pasos. De todo lo que hemos comentado hasta ahora se deduce que el lugar idóneo para realizar la suplantación de las peticiones al sistema es en la función _init(), nada más cargarse nuestro módulo, para intentar perder el menor tiempo posible y controlar la situación desde el principio.

int (*oldopen64) (const char *path, int oflag, mode_t mode);
int (*oldcreat64) (const char *path, mode_t mode);

[...]

int newopen64(const char *path, int oflag, mode_t mode)
{
 [...]
}

int newcreat64(const char *path, mode_t mode)
{
 [...]
}

int _init(void)
{
	int i;

	if  != 0)
		cmn_err(CE_NOTE,"Could not install module\n");

	oldopen64 = (void *) sysent[SYS_open64].sy_callc;
	oldcreat64 = (void *) sysent[SYS_creat64].sy_callc;

	sysent[SYS_open64].sy_callc = (void *) newopen64;
	sysent[SYS_creat64].sy_callc = (void *) newcreat64;

	return i;
}

En este ejemplo el módulo suplanta las syscalls open64() y creat64() de la forma explicada: primeramente guardamos en las funciones oldopen64 y oldcreat64 las funciones que dan servicio a estas syscalls originariamente, para suplantarlas con nuestras nuevas funciones (newopen64 y newcreat64) posteriormente en la tabla de syscalls (sysent).

Áreas de memoria, cómo disponer de memoria en el kernel

Como ya hemos comentado previamente, el espacio de memoria reservado para el kernel está separado del espacio de memoria reservado para los programas de usuario (incluidos los que se ejecutan como superusuario o root). Es por esto que debemos hilar muy fino y no errar al tratar de utilizar contenidos del espacio de memoria de usuario en el kernel y viceversa. Las funciones para reservar memoria también son diferentes dentro del área de memoria del kernel. En lugar de hacer uso de las típicas alloc() o malloc(), dentro del kernel de Solaris deberemos utilizar kmem_alloc():

cadena = (char *) kmem_alloc(tam, KM_SLEEP);

El funcionamiento es muy similar a su función homónima dentro del área de memoria de usuario, con la salvedad del segundo parámetro. Si utilizamos kmem_alloc() con KM_SLEEP como segundo parámetro, estaremos indicando que podemos esperar hasta que realmente haya un bloque de memoria tan grande como el que hayamos pedido. Este es el método más seguro y asegura un éxito en la reserva de memoria a costa de un eventual retardo. Si no queremos esperar, nos arriesgamos a que no se encuentre un bloque tan grande como el que hemos solicitado y algo pueda fallar, sin embargo el retardo es mínimo. En este caso deberemos llamar a kmem_alloc() con el parámetro KM_NOSLEEP. Para el desarrollo de nuestros módulos es aconsejable utilizar KM_SLEEP y asegurar la estabilidad del sistema. En cuanto a la naturaleza del bloque de memoria proporcionado por kmem_alloc() podremos decir que el bloque está alineado como mínimo como double-word y su contenido es aleatorio o no inicializado.

Toda la memoria solicitada deberá ser liberada tras su uso. Como podemos imaginar, las consecuencias de un olvido involuntario serán mucho más graves dentro del kernel. Para liberar memoria utilizaremos la función kmem_free(), indicando el tamaño de bloque que queremos liberar. Hemos de tener mucho cuidado de no liberar más memoria que la que hemos solicitado o de lo contrario estaremos borrando partes esenciales del kernel que pueden afectar a la estabilidad de todo el sistema.

Bien, sabemos cómo reservar memoria dentro del kernel y cómo hacerlo en el espacio de memoria de usuario, pero… ¿cómo podemos hacer transferencias de un espacio de memoria al otro? Algunos módulos del kernel, pobremente programados, utilizan directamente memcpy() para copiar memoria del espacio de memoria de usuario al kernel. Esto funcionaba de manera más o menos aceptable en arquitecturas Solaris 2.7 sobre Intel, pero en Solaris para SPARC provocaba la caída total del sistema. Para realizar una transferencia de información entre los dos espacios de memoria de manera correcta es necesario utilizar las funciones copyin() y copyout(), documentadas dentro de la Interfaz de Drivers de Dispositivos (DDI/DKI).

Un ejemplo práctico del uso de todo esto podría ser el siguiente:

name = (char *) kmem_alloc(256, KM_SLEEP);
copyin(filename, name, 256);
if (!strcmp(name, (char *) oldcmd))
  copyout((char *) newcmd, (char *) filename, strlen(newcmd) + 1);

Queremos comparar el nombre del fichero solicitado (filename) desde el área de memoria de usuario con una cadena de caracteres residente en el área de memoria del kernel (oldcmd) y en caso de que sean iguales, copiar el contenido de newcmd (dentro del kernel) al área de memoria de usuario (filename).

Existen además otras funciones para transferir datos entre las dos áreas de memoria como por ejemplo copyinstr() que nos sirve para copiar cadenas de caracteres acabadas en null. Para más información acerca de este tipo de funciones y su manejo, es preciso consultar la documentación de Solaris para la creación de drivers para dispositivos o en las páginas de manual de alloc(), copyin() y copyout().

Compilación y uso de un LKM

La compilación de un LKM en Solaris es bastante sencilla, únicamente deberemos definir una serie de opciones de compilación (-D_KERNEL -DSVR4 -DSOL2) indicando que se trata de un módulo de kernel y no de un ejecutable estándar. Además, es preciso enlazar o linkar nuestros módulos con la opción –r para que puedan ser cargados dentro del kernel, de lo contrario en linker del kernel no podrá enlazarlos:

bash-2.03# cc -g -D_KERNEL -DSVR4 -DSOL2 -O2 -c quark.c
bash-2.03# ld –o quark -r quark.o

Dado que el kernel de Solaris no provee a los programadores de módulos tantas facilidades como pueda ofrecer el kernel de Linux u otros kernels (el número de funciones que disponemos “dentro del kernel” en Solaris es muy limitado comparado con Linux), es posible que sea necesario utilizar funciones C standard (libC) que no están en el kernel de Solaris. Para ello utilizaremos el comando ar con objeto de extraer las funciones que necesitemos de la librería libC y posteriormente las linkaremos con nuestro módulo:

bash-2.03# ar –x /lib/libc.a memcpy.o strstr.o
bash-2.03# ld –o quark -r quark.o memcpy.o strstr.o

2 pensamientos en “Programación de LKMs en Solaris

  1. Pingback: Módulo del núcleo para Solaris orientado a la seguridad « txipi:blog

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *