¿Por qué no puedes/no quieres usar el truco LD_PRELOAD?
Código de ejemplo aquí:
/*
* File: soft_atimes.c
* Author: D.J. Capelis
*
* Compile:
* gcc -fPIC -c -o soft_atimes.o soft_atimes.c
* gcc -shared -o soft_atimes.so soft_atimes.o -ldl
*
* Use:
* LD_PRELOAD="./soft_atimes.so" command
*
* Copyright 2007 Regents of the University of California
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>
extern int errorno;
int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;
int open(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open) {
_open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
}
if(flags & O_CREAT)
return _open(pathname, flags | O_NOATIME, mode);
else
return _open(pathname, flags | O_NOATIME, 0);
}
int open64(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open64) {
_open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
}
if(flags & O_CREAT)
return _open64(pathname, flags | O_NOATIME, mode);
else
return _open64(pathname, flags | O_NOATIME, 0);
}
Por lo que entiendo... es más o menos el truco LD_PRELOAD o un módulo del kernel. No hay mucho término medio a menos que desee ejecutarlo con un emulador que pueda interceptar su función o reescribir el código en el binario real para interceptar su función.
Suponiendo que no puede modificar el programa y no puede (o no quiere) modificar el kernel, el enfoque LD_PRELOAD es el mejor, suponiendo que su aplicación es bastante estándar y no es realmente una que intenta pasar maliciosamente tu interceptación. (En cuyo caso necesitará una de las otras técnicas).
Valgrind se puede utilizar para interceptar cualquier llamada de función. Si necesita interceptar una llamada al sistema en su producto terminado, esto no servirá de nada. Sin embargo, si intenta interceptar durante el desarrollo, puede ser muy útil. Utilicé con frecuencia esta técnica para interceptar funciones hash para poder controlar el hash devuelto con fines de prueba.
En caso de que no lo sepa, Valgrind se usa principalmente para encontrar pérdidas de memoria y otros errores relacionados con la memoria. Pero la tecnología subyacente es básicamente un emulador x86. Emula su programa e intercepta llamadas a malloc/free, etc. Lo bueno es que no necesita volver a compilar para usarlo.
Valgrind tiene una característica que denominan Envoltura de funciones , que se utiliza para controlar la interceptación de funciones. Consulte la sección 3.2 del manual de Valgrind para obtener más detalles. Puede configurar el ajuste de funciones para cualquier función que desee. Una vez que se intercepta la llamada, se invoca la función alternativa que proporcione.
Primero, eliminemos algunas respuestas negativas que otras personas han dado:
- Usar
LD_PRELOAD
. Sí, dijiste "Además deLD_PRELOAD
..." en la pregunta, pero aparentemente eso no es suficiente para algunas personas. Esta no es una buena opción porque solo funciona si el programa usa libc, lo cual no es necesariamente el caso. - Utilice Systemtap. Sí, dijiste "Además ... Módulos del kernel de Linux" en la pregunta, pero aparentemente eso no es suficiente para algunas personas. Esta no es una buena opción porque tienes que cargar un módulo kernel personalizado que es un gran dolor de cabeza y también requiere root.
- Valgrind. Esto funciona, pero funciona simulando la CPU, por lo que es muy lento y muy complicado. Está bien si solo está haciendo esto para una depuración única. No es realmente una opción si estás haciendo algo digno de producción.
- Varias cosas de auditoría de llamadas al sistema. No creo que registrar llamadas al sistema cuente como "interceptarlas". Claramente queremos modificar los parámetros de syscall/valores devueltos o redirigir el programa a través de algún otro código.
Sin embargo, hay otras posibilidades que aún no se mencionan aquí. Tenga en cuenta que soy nuevo en todo esto y aún no he probado nada, por lo que puedo estar equivocado en algunas cosas.
Reescribe el código
En teoría, podría usar algún tipo de cargador personalizado que reescriba las instrucciones de llamada al sistema para saltar a un controlador personalizado. Pero creo que sería una auténtica pesadilla implementarlo.
ksondeos
kprobes son una especie de sistema de instrumentación del núcleo. Solo tienen acceso de solo lectura a cualquier cosa, por lo que no puede usarlos para interceptar llamadas del sistema, solo registrarlas.
pseguimiento
ptrace es la API que usan los depuradores como GDB para hacer su depuración. Hay un PTRACE_SYSCALL
opción que pausará la ejecución justo antes/después de las llamadas al sistema. A partir de ahí, puedes hacer prácticamente lo que quieras de la misma manera que GDB. Aquí hay un artículo sobre cómo modificar los parámetros de syscall usando ptrace. Sin embargo, aparentemente tiene una gran sobrecarga.
Seccomp
Seccomp es un sistema diseñado para permitirle filtrar llamadas al sistema. No puede modificar los argumentos, pero puede bloquearlos o devolver errores personalizados. Los filtros Seccomp son programas BPF. Si no está familiarizado, son básicamente programas arbitrarios que los usuarios pueden ejecutar en una VM del espacio del kernel. Esto evita el cambio de contexto de usuario/núcleo que los hace más rápidos que ptrace.
Si bien no puede modificar los argumentos directamente desde su programa BPF, puede devuelve SECCOMP_RET_TRACE
que activará un ptrace
ing padre para romper. Entonces es básicamente lo mismo que PTRACE_SYSCALL
excepto que puede ejecutar un programa en el espacio del kernel para decidir si realmente desea interceptar una llamada al sistema en función de sus argumentos. Por lo tanto, debería ser más rápido si solo desea interceptar algunas llamadas al sistema (por ejemplo, open()
con rutas específicas).
Creo que esta es probablemente la mejor opción. Aquí hay un artículo al respecto del mismo autor que el anterior. Ten en cuenta que usan BPF clásico en lugar de eBPF, pero supongo que también puedes usar eBPF.
Editar:en realidad solo puedes usar BPF clásico, no eBPF. Hay un artículo de LWN al respecto.
Aquí hay algunas preguntas relacionadas. Definitivamente vale la pena leer el primero.
- ¿Puede eBPF modificar el valor devuelto o los parámetros de una llamada al sistema?
- Interceptar solo llamadas al sistema con PTRACE_SINGLESTEP
- ¿Es esta una buena forma de interceptar llamadas del sistema?
- Mínima sobrecarga para interceptar llamadas al sistema sin modificar el kernel
También hay un buen artículo sobre la manipulación de llamadas al sistema a través de ptrace aquí.
Algunas aplicaciones pueden engañar a strace/ptrace para que no se ejecute, por lo que la única opción real que he tenido es usar systemtap
Systemtap puede interceptar un montón de llamadas al sistema si es necesario debido a su coincidencia de comodines. Systemtap no es C, sino un lenguaje separado. En modo básico, systemtap debería evitar que hagas cosas estúpidas, pero también puede ejecutarse en "modo experto" que recurre a permitir que un desarrollador use C si es necesario.
No requiere que parchee su kernel (o al menos no debería), y una vez que se ha compilado un módulo, puede copiarlo desde un cuadro de prueba/desarrollo e insertarlo (a través de insmod) en un sistema de producción.
Todavía tengo que encontrar una aplicación de Linux que haya encontrado una manera de solucionar/evitar ser atrapado por systemtap.