todos - modulos cargados linux
¿Cómo cargar módulos de kernel de Linux desde código C? (5)
No estoy seguro de que haya una forma más limpia que el system
.
Pero seguro, si desea cargar / descargar los módulos de su demonio de espacio de usuario, entonces se obliga a ejecutar el demonio como root *, lo que no puede considerarse como seguro.
*: o puede agregar los comandos explícitos en el archivo sudoers, pero esto será una pesadilla para administrar al implementar su aplicación.
Tengo una aplicación que tiene dos módulos de kernel externos y un demonio de espacio de usuario. Quiero cargar los módulos desde el código del daemon, escrito en C, al inicio, y descargarlos en la salida limpia. ¿Puedo cargarlos de una forma más limpia que haciendo el system("modprobe module");
¿Y descargarlos usando el rmmod
correspondiente?
Puede realizar las mismas tareas que modprobe
y Co., pero dudo que se pueda caracterizar como más limpio .
Recomiendo el uso de system()
en cualquier código de demonio que se ejecute con permisos de raíz, ya que es relativamente fácil de explotar desde un punto de vista de seguridad. modprobe
y rmmod
son, de hecho, las herramientas adecuadas para el trabajo. Sin embargo, sería un poco más limpio y mucho más seguro utilizar un fork()
explícito fork()
+ exec()
para invocarlos.
insmod / rmmod usa las funciones init_module
y delete_module
para hacer esto, que también tienen una página de manual disponible. Ambos declaran las funciones como extern
lugar de incluir un encabezado, pero la página de manual dice que deberían estar en <linux/module.h>
.
Ejemplo ejecutable mínimo
Probado en una máquina virtual QEMU + Buildroot VM y Ubuntu 16.04 con este sencillo módulo de impresora de parámetros .
Utilizamos las llamadas al sistema init_module
/ finit_module
y remove_module
Linux .
El kernel de Linux ofrece dos llamadas al sistema para la inserción del módulo:
-
init_module
-
finit_module
y:
man init_module
documentos que:
La llamada del sistema finit_module () es como init_module (), pero lee el módulo que se va a cargar desde el descriptor de archivos fd. Es útil cuando la autenticidad de un módulo del kernel se puede determinar a partir de su ubicación en el sistema de archivos; en los casos en que sea posible, se puede evitar la sobrecarga de usar módulos firmados criptográficamente para determinar la autenticidad de un módulo. El argumento param_values es como para init_module ().
finit
es más nuevo y se agregó solo en v3.8. Más razones: https://lwn.net/Articles/519010/
glibc no parece proporcionar un envoltorio de C para ellos, así que solo creamos el nuestro con syscall
.
insmod.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)
#define finit_module(fd, param_values, flags) syscall(__NR_finit_module, fd, param_values, flags)
int main(int argc, char **argv) {
const char *params;
int fd, use_finit;
size_t image_size;
struct stat st;
void *image;
/* CLI handling. */
if (argc < 2) {
puts("Usage ./prog mymodule.ko [args="" [use_finit=0]");
return EXIT_FAILURE;
}
if (argc < 3) {
params = "";
} else {
params = argv[2];
}
if (argc < 4) {
use_finit = 0;
} else {
use_finit = (argv[3][0] != ''0'');
}
/* Action. */
fd = open(argv[1], O_RDONLY);
if (use_finit) {
puts("finit");
if (finit_module(fd, params, 0) != 0) {
perror("finit_module");
return EXIT_FAILURE;
}
close(fd);
} else {
puts("init");
fstat(fd, &st);
image_size = st.st_size;
image = malloc(image_size);
read(fd, image, image_size);
close(fd);
if (init_module(image, image_size, params) != 0) {
perror("init_module");
return EXIT_FAILURE;
}
free(image);
}
return EXIT_SUCCESS;
}
rmmod.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#define delete_module(name, flags) syscall(__NR_delete_module, name, flags)
int main(int argc, char **argv) {
if (argc != 2) {
puts("Usage ./prog mymodule");
return EXIT_FAILURE;
}
if (delete_module(argv[1], O_NONBLOCK) != 0) {
perror("delete_module");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Interpretación de la fuente de Busybox
Busybox proporciona insmod
, y ya que está diseñado para el minimalismo, podemos tratar de deducir cómo se hace desde allí.
En la versión 1.24.2, el punto de entrada está en modutils/insmod.c
función insmod_main
.
IF_FEATURE_2_4_MODULES
es un soporte opcional para los módulos más antiguos del kernel 2.4 de Linux, por lo que podemos ignorarlo por ahora.
Eso solo reenvía a la función bb_init_module
.
bb_init_module
intenta dos cosas:
mmap
el archivo a la memoria a través detry_to_mmap_module
.Esto siempre establece
image_size
al tamaño del archivo.ko
como efecto secundario.si eso falla,
malloc
el archivo a la memoria conxmalloc_open_zipped_read_close
.Esta función, opcionalmente, descomprime el archivo primero si es un archivo zip y, de lo contrario, lo hace mallocs.
No entiendo por qué se hace este negocio de compresión, ya que ni siquiera podemos confiar en él porque el
try_to_mmap_module
no parece descomprimir las cosas.
Finalmente llega la llamada:
init_module(image, image_size, options);
donde la image
es el ejecutable que se guardó en la memoria, y las opciones son solo ""
si llamamos insmod file.elf
sin más argumentos.
Se proporciona init_module
arriba por:
#ifdef __UCLIBC__
extern int init_module(void *module, unsigned long len, const char *options);
extern int delete_module(const char *module, unsigned int flags);
#else
# include <sys/syscall.h>
# define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
# define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)
#endif
ulibc
es una implementación de libc incrustada, y parece proporcionar init_module
.
Si no está presente, creo que se supone glibc, pero como man init_module
dice:
Glibc no admite la llamada al sistema init_module (). No se proporciona ninguna declaración en los encabezados de glibc, pero, a través de una peculiaridad de la historia, glibc exporta un ABI para esta llamada de sistema. Por lo tanto, para emplear esta llamada al sistema, es suficiente declarar manualmente la interfaz en su código; como alternativa, puede invocar la llamada del sistema utilizando syscall (2).
BusyBox sigue sabiamente ese consejo y usa syscall
, que proporciona glibc, y que ofrece una API de C para llamadas al sistema.