Dirtycow ejecuta (lib-c a root) bien pero se bloquea al reiniciar

2

Tengo un servidor ubuntu 14.04.3 ejecutándose en una máquina virtual con la versión kernel 3.13.0-83-genérica en ejecución. He probado varios PoC reunidos aquí . La mayoría de ellos rompen el núcleo (no todas las veces, pero a veces) y no son confiables; Excepto por el lib-c para root exploit. Este explota la vulnerabilidad con éxito y saca un shell raíz por unos segundos (unos 20-30 segundos). Para hacerlo estable hice echo 0 > /proc/sys/vm/dirty_writeback_centisecs como se mencionó aquí . Todo está bien hasta que reinicies. Al reiniciar, el kernel se bloquea :(

Tengo 2 preguntas:

Primero, ¿de qué dependen los choques? ¿Es el hardware? ¿La versión del kernel? ¿El exploit?

En segundo lugar, ¿cómo puedo corregir el bloqueo del kernel al reiniciar mientras estoy usando la lib-c para explotar la raíz?

ACTUALIZACIÓN 1

Esto es lo que obtengo con kdump:

[  388.077362] kernel BUG at /build/linux-03BQvT/linux-3.13.0/fs/ext4/inode.c:2420!
[  388.077497] invalid opcode: 0000 [#1] SMP 
[  388.077601] Modules linked in: crct10dif_pclmul crc32_pclmul vmw_balloon aesni_intel aes_x86_64 lrw gf128mul glue_helper ablk_helper cryptd serio_raw vmw_vmci lp parport psmouse ahci e1000 libahci floppy mptspi mptscsih mptbase
[  388.078190] CPU: 1 PID: 453 Comm: kworker/u256:28 Not tainted 3.13.0-83-generic #127-Ubuntu
[  388.078426] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 05/20/2014
[  388.078627] Workqueue: writeback bdi_writeback_workfn (flush-8:0)
[  388.078755] task: ffff880135e69800 ti: ffff880135e70000 task.ti: ffff880135e70000
[  388.078878] RIP: 0010:[<ffffffff81241298>]  [<ffffffff81241298>] mpage_prepare_extent_to_map+0x2b8/0x2c0
[  388.079027] RSP: 0018:ffff880135e719d8  EFLAGS: 00010246
[  388.079102] RAX: 01ffff000002007d RBX: ffff880135e71a18 RCX: 0000000000000000
[  388.079187] RDX: ffff880135e71a18 RSI: 0000000000000000 RDI: ffff8801377824a0
[  388.079272] RBP: ffff880135e71aa8 R08: 0000000000000000 R09: 0000000000000000
[  388.079357] R10: 0000000000000100 R11: 0000000000000210 R12: 0000000000003400
[  388.079441] R13: 0007ffffffffffff R14: ffffea0002ec8c80 R15: ffff880135e71b50
[  388.079527] FS:  0000000000000000(0000) GS:ffff88013a620000(0000) knlGS:0000000000000000
[  388.079651] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  388.079729] CR2: 0000000000410000 CR3: 00000000377b5000 CR4: 00000000001407e0
[  388.079852] Stack:
[  388.079912]  ffff880135e71a18 0000000000000000 ffff880137782498 ffff880135e71a18
[  388.080089]  0000000000000001 0000000000000001 0000000000000000 ffffea0002ec8c80
[  388.080265]  ffff8800bba09000 ffff880135e71a68 ffffffff81288bc3 ffff880100000050
[  388.080441] Call Trace:
[  388.080506]  [<ffffffff81288bc3>] ? jbd2__journal_start+0xf3/0x1e0
[  388.080587]  [<ffffffff81245276>] ? ext4_writepages+0x3c6/0xd20
[  388.080667]  [<ffffffff8126f7f9>] ? __ext4_journal_start_sb+0x69/0xe0
[  388.080749]  [<ffffffff812452a2>] ext4_writepages+0x3f2/0xd20
[  388.080830]  [<ffffffff8115bc2e>] do_writepages+0x1e/0x40
[  388.080907]  [<ffffffff811e7f10>] __writeback_single_inode+0x40/0x220
[  388.080989]  [<ffffffff811e8cd7>] writeback_sb_inodes+0x247/0x3e0
[  388.081069]  [<ffffffff811e8f0f>] __writeback_inodes_wb+0x9f/0xd0
[  388.081149]  [<ffffffff811e9183>] wb_writeback+0x243/0x2c0
[  388.081228]  [<ffffffff810870c6>] ? set_worker_desc+0x76/0x90
[  388.081307]  [<ffffffff811ea9a8>] bdi_writeback_workfn+0x108/0x430
[  388.081388]  [<ffffffff81083d22>] process_one_work+0x182/0x450
[  388.081468]  [<ffffffff81084b11>] worker_thread+0x121/0x410
[  388.081545]  [<ffffffff810849f0>] ? rescuer_thread+0x430/0x430
[  388.081624]  [<ffffffff8108b8f2>] kthread+0xd2/0xf0
[  388.081706]  [<ffffffff8108b820>] ? kthread_create_on_node+0x1c0/0x1c0
[  388.081787]  [<ffffffff817364e8>] ret_from_fork+0x58/0x90
[  388.081861]  [<ffffffff8108b820>] ? kthread_create_on_node+0x1c0/0x1c0
[  388.081940] Code: 00 00 00 48 8d bd 58 ff ff ff 89 85 48 ff ff ff e8 6e cf f1 ff 8b 85 48 ff ff ff eb ca 48 8d bd 58 ff ff ff e8 5a cf f1 ff eb 80 <0f> 0b 0f 0b 0f 1f 40 00 0f 1f 44 00 00 55 48 89 e5 41 57 41 56 
[  388.083376] RIP  [<ffffffff81241298>] mpage_prepare_extent_to_map+0x2b8/0x2c0
[  388.083472]  RSP <ffff880135e719d8>
    
pregunta arashkgpt 06.11.2016 - 15:07
fuente

2 respuestas

2

TL; DR se produjo una condición de carrera que hizo que el bit de reescritura se estableciera en una página inmediatamente después de esperar a que se desarmara el bit, tropezando con una comprobación de validez que bloqueó el kernel.

Desafortunadamente, no puedo responder a sus dos preguntas, ya que requieren mucha más depuración, pero puedo aclarar por qué ocurre esto, lo que puede ayudar. Parece probable que esto se active cuando el kernel sincroniza los sistemas de archivos justo antes de reiniciar. Puede probar esto ejecutando sync usted mismo (o realizando una sincronización de emergencia, es decir, sysrq-s) para ver si eso provoca este comportamiento, o reiniciando sin sincronizar primero, es decir, con reboot -n y ver si no se produce un bloqueo.

El problema en sí mismo

Esto parece provenir de un BUG() en fs/ext4/inode.c:mpage_prepare_extent_to_map . La macro BUG() se usa para saltar intencionalmente el núcleo generando una excepción de instrucción ilegal. Está destinado a ser utilizado como un control de sanidad en tiempo de ejecución. La macro BUG() incondicionalmente genera una excepción, mientras que BUG_ON() toma un solo argumento y genera la excepción si el argumento se evalúa como verdadero.

En el registro que publicaste, se activa BUG_ON(PageWriteback(page)) , lo que significa que su argumento se evaluó como verdadero. Esto significa que, en este punto del código, PageWriteback(page) siempre debe devolver false. Esto se utiliza para evitar una escritura simultánea de la misma página. Las líneas relevantes son:

wait_on_page_writeback(page);
BUG_ON(PageWriteback(page));

La primera línea es una función definida en include/linux/pagemap.h :

/*
 * Wait for a page to complete writeback
 */
static inline void wait_on_page_writeback(struct page *page)
{
    if (PageWriteback(page))
        wait_on_page_bit(page, PG_writeback);
}

La segunda línea proviene de include/linux/page-flags.h :

#define TESTPAGEFLAG(uname, lname, policy)                             \
    static __always_inline int Page##uname(struct page *page)          \
    { return test_bit(PG_##lname, &policy(page, 0)->flags); }

La raíz del problema

Desafortunadamente, la razón exacta por la que esto ocurre no me queda clara. A juzgar por cómo funciona el exploit de DirtyCow, tiene sentido, ya que parece obvio que esto es el resultado de una condición de carrera (de lo contrario, la página no debería recuperar repentinamente el bit de reescritura después de esperar a que se elimine, a menos que se indique explícitamente). Desearía realizar una depuración más exhaustiva para comprender el problema completamente. Como es probable que se trate de una condición de carrera, no tendrás mucha suerte con solo este registro de Oops. Tendrá que mirar el código (sin fallas, tan silencioso) que se está ejecutando en otra CPU que está activando esto.

Puedo decir que resultó en uno de los tres eventos posibles que desencadenaron este resultado:

  • La página de destino no tenía el bit de reescritura establecido. Como resultado, wait_on_page_writeback() actuó como un no-op. De alguna manera, el bit se estableció poco después de que se devolvió la función.
  • La página de destino tenía el bit de reescritura establecido. El wait_on_page_writeback() esperó hasta que se eliminó el bit antes de regresar. De alguna manera, después de que se eliminó el bit, se estableció de nuevo.
  • La página de destino tenía el bit de reescritura establecido, y se suponía que wait_on_page_writeback() regresaba solo cuando el bit estaba desactivado. De alguna manera, volvió a pesar de que el bit aún estaba configurado.

Independientemente de cuál de estos sea el caso, la función wait_on_page_writeback() regresó, sin embargo, la página aún tenía o recuperó rápidamente el bit de reescritura. Esto fue detectado por un BUG_ON() sanity check porque nunca debería suceder.

Cómo el registro de Oops revela esto

En lugar de simplemente decir "esto es lo que es", explicaré cómo recopilé esto del registro que publicaste.

[  388.077362] kernel BUG at /build/linux-03BQvT/linux-3.13.0/fs/ext4/inode.c:2420!
[  388.077497] invalid opcode: 0000 [#1] SMP 

Esto me dice que el problema fue una comprobación de validez relacionada con BUG() . La primera línea lo señala explícitamente, e incluso apunta a la línea exacta y al archivo fuente de interés. La segunda línea también refuerza esto implícitamente, porque BUG() y las funciones relacionadas ejecutan la instrucción ud2 , lo que resulta en un error de opcode (instrucción) no válido.

[  388.078878] RIP: 0010:[<ffffffff81241298>]  [<ffffffff81241298>] mpage_prepare_extent_to_map+0x2b8/0x2c0

El RIP es el puntero de instrucción (llamado EIP y IP en los procesadores de 32 y 16 bits, respectivamente). Apuntará a la ubicación de la instrucción que se está ejecutando actualmente, que estará dentro de la función actual. Como tengo una versión del kernel diferente y no quería buscar su versión exacta, pude ir a la función mpage_prepare_extent_to_map() en fs/ext4/inode.c y buscar algo como BUG() o BUG_ON() , así como cualquier línea justo por encima de ella. Buscar los archivos donde se definen las dos funciones wait_on_page_writeback() y PageWriteback() reveló su propósito. Específicamente, estas garantías proporcionadas por las dos líneas de código fuente resultaron ser incorrectas:

  • Después de que wait_on_page_writeback(page) devuelva, page no tendrá el bit de reescritura establecido.
  • PageWriteback(page) devolverá false, ya que page no tendrá el bit de reescritura establecido.
  • Por lo tanto, BUG_ON(PageWriteback(page)) no se activará.

Debido a que se activó BUG_ON() , el problema se vuelve obvio.

    
respondido por el forest 03.12.2017 - 10:31
fuente
0
  

¿De qué dependen los choques? ¿Es el hardware? ¿La versión del kernel? ¿El exploit?

Para obtener información sobre el bloqueo del kernel, debe utilizar kdump-tools , se puede encontrar un tuto completo aquí

  

¿Cómo puedo corregir el bloqueo del kernel al reiniciar mientras estoy usando lib-c para root exploit?

Debería arrancar desde un Linux live cd / usb, cree su entorno chroot y luego reinstale linux-image .

    
respondido por el GAD3R 06.11.2016 - 17:51
fuente

Lea otras preguntas en las etiquetas