Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2023-2598 — Análisis técnico y exploit de prueba de concepto para CVE-2023-2598, una vulnerabilidad de escalada de privilegios en el kernel de Linux en el registro de búfer de io_uring, con explicación detallada de los internals de Compound Page y folio. | Kitploit
Herramientas/GitHubGitHub/cainiao159357/cve-2023-2598
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónPapers e InvestigaciónAprendizaje y EducaciónExplotación de Binarios
GitHubcainiao159357/cve-2023-2598

CVE-2023-2598

Análisis técnico y exploit de prueba de concepto para CVE-2023-2598, una vulnerabilidad de escalada de privilegios en el kernel de Linux en el registro de búfer de io_uring, con explicación detallada de los internals de Compound Page y folio.

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
20hace 2 añosAún no revisado

CVE-2023-2598 Escalada de privilegios

A través de CVE-2023-2598, comprender los mecanismos de Compound Page y folio en Linux, y posteriormente ver si se puede completar la explotación del 1day CVE-2023-6560.

Compound Page(huge page)

La memoria es cada vez mayor, pero la unidad básica de asignación de páginas de Linux sigue siendo de 4K, lo que resulta insuficiente. Por ello, se introdujeron las páginas compuestas (compound pages) para resolver este problema. Una página compuesta consiste básicamente en tratar varias páginas como un conjunto, combinando dos o más páginas físicamente contiguas en una unidad que, en muchos aspectos, puede considerarse como una única página más grande. Se utilizan con mayor frecuencia para crear páginas grandes, como en los subsistemas hugetlbfs o de páginas grandes transparentes (transparent huge pages), pero también aparecen en otros escenarios. Las páginas compuestas pueden usarse como memoria anónima o como buffers en el kernel; sin embargo, no pueden aparecer en el page cache, ya que este solo puede manejar páginas individuales.

Asignar una página compuesta consiste en llamar a alloc_pages() con el flag de asignación __GFP_COMP y un número de marcos de página mayor que 1, es decir, un order de al menos 1. Esto viene determinado por el mecanismo de implementación de las páginas compuestas.

Nota: las páginas compuestas siempre son físicamente contiguas

El flag del primer page marca PG_head, indicando que se trata de la head page de la página compuesta;

Todos los pages posteriores se configuran con dos atributos: mapping y compound_head, y mediante compound_head se confirma si es una tail page o una head page; para más detalle, consulta la función compound_head();

En el segundo page se almacena más información de la página compuesta, razón por la cual el order de una página compuesta debe ser al menos 1;

static inline unsigned long _compound_head(const struct page *page)
{
        unsigned long head = READ_ONCE(page->compound_head);
 
        if (unlikely(head & 1))
                return head - 1;
        return (unsigned long)page;
}

Se puede ver que este campo no solo contiene el flag, sino también un puntero a la head page.

Por lo tanto, al obtener un page, se puede determinar fácilmente si es una página compuesta y, de serlo, si es una head page o una tail page. Pero aún falta una información clave: el tamaño de la página compuesta. Si no se conoce el tamaño de la página compuesta, se necesitará conocerlo al liberarla (free). Toda esta información se almacena en el campo lru de la primera tail page: el tamaño (order) de la página compuesta se convierte primero al tipo puntero y se guarda en lru.prev, mientras que el destructor se guarda en lru.next.

Con solo conocer la head page y el tamaño de la página compuesta, se puede liberar correctamente esta página grande, porque las páginas compuestas siempre son físicamente contiguas.

La estructura se muestra en la siguiente figura:

img

folio

El folio puede verse como una capa de envoltura sobre page, sin sobrecarga alguna. Un folio puede ser una sola página o una página compuesta.

img

La figura anterior es un diagrama esquemático de la estructura page. Sus 64 bytes gestionan información como flags, lru, mapping, index, private, {ref_, map_}count, memcg_data, etc. Cuando el page es una página compuesta, la información mencionada (flags, etc.) se encuentra en la head page, mientras que la tail page reutiliza la gestión de información como compound_{head, mapcount, order, nr, dtor}, etc.

struct folio {
        /* private: don't document the anon union */
        union {
                struct {
        /* public: */
                        unsigned long flags;
                        struct list_head lru;
                        struct address_space *mapping;
                        pgoff_t index;
                        void *private;
                        atomic_t _mapcount;
                        atomic_t _refcount;
#ifdef CONFIG_MEMCG
                        unsigned long memcg_data;
#endif
        /* private: the union with struct page is transitional */
                };
                struct page page;
        };
};

En la definición de la estructura folio, la información como flags, lru, etc., es exactamente igual que en page, por lo que se puede hacer union con page. De esta manera, se puede usar directamente folio->flags sin tener que usar folio->page->flags.

#define page_folio(p)           (_Generic((p),                          \
        const struct page *:    (const struct folio *)_compound_head(p), \
        struct page *:          (struct folio *)_compound_head(p)))

#define nth_page(page,n) ((page) + (n))
#define folio_page(folio, n)    nth_page(&(folio)->page, n)

A primera vista, page_folio puede resultar un poco confuso; en realidad es equivalente a:

switch (typeof(p)) {
  case const struct page *:
    return (const struct folio *)_compound_head(p);
  case struct page *:
    return (struct folio *)_compound_head(p)));
}

A través de la definición de la macro page_folio, se puede descubrir que un folio es en realidad la head page de una página compuesta. Cuando se convierte un folio en page, folio->page se usa para obtener la head page, y folio_page(folio, n) se puede usar para obtener la tail page.

Entonces, ¿para qué sirve el folio? Sobre todo, por consideraciones de desarrollo y eficiencia. Sin folio, las funciones no pueden determinar si el page actual es una head page, por lo que necesitan llamar a _compound_head. Si hay muchas rutas de ejecución, que cada función de la ruta use _compound_head afectaría a la eficiencia. En cambio, si la función solo acepta un parámetro struct folio *, ese folio apunta a la head page, por lo que la función ya no necesita llamar a _compound_head.

Por lo tanto, cumple principalmente estas tres funciones:

  1. Reducir el exceso de llamadas redundantes a compound_head.

  2. Dar una pista al desarrollador: al ver un folio, se puede afirmar que es la head page.

  3. Corregir posibles bugs causados por tail pages.

Principio de la vulnerabilidad

En io_uring_register_buffer de io_uring, existe esta lógica:

image-20240830214419290

Cuando el número de páginas pasadas desde el espacio de usuario es mayor que 1, io_uring comprueba si el buffer pasado es un folio. El método de comprobación consiste en usar page_folio() para obtener la head page de page[i]; si la head page de page[i] es igual a page[0], se considera que ambas pertenecen a la misma página compuesta.

En general, este manejo no presenta problemas, pero existe un caso especial: si en el espacio de usuario se usa mmap para mapear la misma página física en direcciones virtuales contiguas, también se cumple esta condición de comprobación, por lo que finalmente se entra en esta rama:

image-20240830225137539

En este momento, el espacio de usuario solo ha asignado una página física, pero el size final es el size de las direcciones virtuales contiguas, lo que provoca que size pueda ser mayor que la región de direcciones físicas realmente asignada. Finalmente, esto causa una lectura/escritura fuera de los límites (out-of-bounds).

Explotación de la vulnerabilidad

Descargar herramienta