¡Rescata el pánico del kernel de Linux manualmente!

A veces, el pánico del kernel no tiene por qué ser un pánico verdadero. Por ejemplo, si se produce un acceso de violación de memoria en su propio módulo, está seguro de que el pánico no afectará a todo el kernel y que el radio de daño es suficiente para converger. El pánico puede tener diferentes comportamientos:

  • Entregue la tarea actual directamente al programa.

Aunque hacerlo en un contexto de interrupción puede dañar procesos inocentes de modo que ya no se puedan volver a programar, es mejor que un reinicio completo.

El siguiente código muestra cómo hacerlo:

// panic_resched.c
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/sched.h>
#include <linux/preempt_mask.h>
#include <linux/cpu.h>

char *stub = NULL;
char *addr_user = NULL;

void stub_panic(const char *fmt, ...)
{
    
    
	if (0 /* 破坏了内核共享数据 */)
		return;
	if (0 && preempt_count()) // 为了测试sysrq-Crash,不得不先禁用这个。
		return;
	// 只要允许,就不要真的panic,而是直接schedule出去。
	__set_current_state(TASK_UNINTERRUPTIBLE);
	local_irq_enable();
	schedule();
	// 由于current不再RUNNING,不会再被调度,永远不会到这里。
}

#define FTRACE_SIZE   	5
#define POKE_OFFSET		0
#define POKE_LENGTH		5

static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);

unsigned char jmp_call[POKE_LENGTH];
unsigned char orig_call[POKE_LENGTH];

static int __init panic_resched_init(void)
{
    
    
	s32 offset;

	addr_user = (void *)kallsyms_lookup_name("panic");
	if (!addr_user) {
    
    
		return -1;
	}

	_text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
	if (!_text_poke_smp) {
    
    
		return -1;
	}

	stub = (void *)stub_panic;

	jmp_call[0] = 0xe8;

	offset = (s32)((long)stub - (long)addr_user - FTRACE_SIZE);
	(*(s32 *)(&jmp_call[1])) = offset;

	memcpy(orig_call, &addr_user[POKE_OFFSET], POKE_LENGTH);
	get_online_cpus();
	_text_poke_smp(&addr_user[POKE_OFFSET], jmp_call, POKE_LENGTH);
	put_online_cpus();

	return 0;
}

static void __exit panic_resched_exit(void)
{
    
    

	get_online_cpus();
	_text_poke_smp(&addr_user[POKE_OFFSET], orig_call, POKE_LENGTH);
	put_online_cpus();
}

module_init(panic_resched_init);
module_exit(panic_resched_exit);
MODULE_LICENSE("GPL");

Ven y mira el efecto:

[root@localhost test]# insmod ./panic_resched.ko
[root@localhost test]# echo c >/proc/sysrq-trigger &
[1] 1617
[root@localhost test]# ps -elf|grep 1617
1 D root      1617  1598  0  80   0 - 28894 stub_p 19:11 pts/1    00:00:00 -bash # D住了而已
...

Obviamente, este pánico realmente no se derrumbó, solo se acabó el horario.

Tenga en cuenta los comentarios en el código:

if (0 && preempt_count()) // 为了测试sysrq-Crash,不得不先禁用这个。

Dado que sysrq desencadena un bloqueo y otras acciones, mantendrá un spinlock y spinlock desactivará la preferencia, por lo que cuando sysrq desencadena un bloqueo, su preempt_count () no es cero.

Podemos omitirlo con el siguiente código:

while (preempt_count())
	preempt_enable_no_resched();

OK, el gancho completo es:

void stub_panic(const char *fmt, ...)
{
    
    
	if (0 /* 破坏了内核共享数据 */)
		return;
	while (preempt_count())
		preempt_enable_no_resched();
	// 只要允许,就不要真的panic,而是直接schedule出去。
	__set_current_state(TASK_UNINTERRUPTIBLE);
	local_irq_enable();
	schedule();
	// 由于current不再RUNNING,不会再被调度,永远不会到这里。
}

Sin embargo, las cosas aún no son perfectas. Si la corriente tiene un bloqueo de giro cuando hay pánico, entonces cuando hay lógica para buscar el mismo bloqueo de giro, se bloqueará, porque el contexto del pánico actual ya no puede volver. ¿Cómo puede ser esto? ¿hacer?

En cuanto a la lógica de cómo desbloquear, lo hablaré por separado más adelante, el problema ahora es que no hay necesidad de hacerlo tan perfecto.

  • El pánico de gancho no es un requisito funcional, es solo una lógica de mejor esfuerzo.
  • Hook pánico es más para hacer cosas malas, confundir a los subordinados de operación y mantenimiento y gerentes, y sesgar su pensamiento sobre la resolución de problemas, ocultando así su verdadero rootkit.

De hecho, para el propósito anterior, a veces el estancamiento es algo bueno.

Si su rootkit es inestable y entra en pánico, entonces el gancho en este artículo ocultará el hecho del pánico y evitará la generación de vmcore, distorsionando por la fuerza el pensamiento de operación y mantenimiento y los gerentes a "¿Eh? Cómo morir". ¿Encerrado? ”En este ritmo.

Jaja, eso es todo.

Oye, ¿qué pasa? ¿Por qué no usas stap esta vez? O al menos use kprobe, no use las cosas listas para usar, ¿por qué tiene que jugar ganchos binarios manualmente?

No necesita herramientas si puede hacerlo manualmente, porque las herramientas son incontrolables. Sabemos que stap y su kprobe subyacente usan el marco ftrace, y la ruta desde el punto de enlace hasta la llamada real a la función de devolución de llamada ftrace es extremadamente larga, y no se puede garantizar si hay un spinlock, semáforo, etc., o algunos variable de un disparo.

Ya sabes, nuestra función de gancho no volverá nunca más. Si el contexto guardado frente a ftrace no se puede restaurar después de que regrese el gancho, el sistema estará fuera de control. Además, si el código del estudiante de primer año ftrace está allí, la escoria puede ver que la función es pasiva.

Una vez más, la forma de jugar del artesano es hacer el trabajo puramente manual tanto como sea posible, independiente y confiable.


Los zapatos de cuero en Wenzhou, Zhejiang están mojados, por lo que no engordan con la lluvia.

Supongo que te gusta

Origin blog.csdn.net/dog250/article/details/108349046
Recomendado
Clasificación