libminikin: 10 años de vulnerabilidad en el núcleo de Android

Friday, July 10, 2026
libminikin: "Un comportamiento heredado del motor de composición de texto de Android"
Análisis forense del código fuente de AOSP · 6 archivos · 5 capas vulnerables · Una década de deuda técnica
Manuel Garcia Peña (Lostmon) · lostmon@gmail.com · lostmon.blogspot.com
10 de julio de 2026

📌 Este artículo es una extensión de mi análisis original: "Algorithmic DoS en libminikin.so – Cómo un texto de 70 KB puede congelar casi cualquier app Android" (24 de junio de 2026).

🔥 10 AÑOS. 6 ARCHIVOS. 5 CAPAS VULNERABLES.

El análisis del código fuente de Android (AOSP) confirma que libminikin.so tiene un fallo de diseño sistémico presente desde 2013.

  • 6 archivos vulnerables en el repositorio de AOSP
  • 5 capas del pipeline de texto sin validación de entrada
  • 10 años de deuda técnica documentada (2013 → 2026)
  • 2.500 millones de dispositivos en riesgo
  • Google lo sabe desde 2016 (CVE-2016-2414) y no lo ha arreglado

Whitepaper completo: 30 de julio de 2026 — lostmon.blogspot.com

1. Resumen ejecutivo

Desde la publicación del artículo original sobre el Algorithmic DoS en libminikin.so, he continuado investigando el código fuente de Android (AOSP) para comprender la magnitud real del problema.

Lo que he encontrado es más grave de lo que imaginaba. No se trata de un bug aislado en una función concreta. Es un fallo de diseño sistémico presente en 6 archivos diferentes, distribuido en 5 capas del pipeline de texto, y que ha estado en el repositorio de AOSP desde 2013.


2. Issues públicos de Google (2020–2026)

La siguiente tabla recopila los issues públicos del Google Issue Tracker que documentan problemas en libminikin y el pipeline de texto de Android. Todos ellos fueron cerrados sin una solución estructural.

Issue Año Descripción Estado Enlace
#161830416 2020 Crash en LayoutCache::getOrCreate con traza completa (FreeType → Minikin → HarfBuzz) Won't Fix Ver issue
#167014931 2020 Crash por Float.POSITIVE_INFINITY en LayoutPiece::LayoutPiece Fixed Ver issue
#188985643 2021 SIGSEGV en FontFamily::getClosestMatch en Samsung Galaxy J6+ con fuente personalizada Won't Fix Ver issue
#40268980 (Chromium 1447465) 2023 ANR en Chrome por LayoutCache::getOrCreate reportado por ingeniero de Samsung Won't Fix Ver issue
#477202817 2026 Reporte formal al Chrome VRP — DoS persistente por URL larga. Cerrado como "Intended Behavior" Won't Fix Ver issue
#524288518 2026 Reporte específico de libminikin al Android VRP — cerrado como "out of scope" Out of Scope (interno de Google)
#531319203 Jul 2026 system_server boot loop por imagen grande en BitmapCache — mismo patrón Assigned Ver issue

🔴 Patrón común: Google ha cerrado sistemáticamente estos reportes sin abordar la causa raíz. Solo ha arreglado el caso trivial (#167014931) porque el trigger era evidente (Float.POSITIVE_INFINITY).


3. Análisis del código fuente: 6 archivos vulnerables

He analizado el código fuente de Minikin en el repositorio de AOSP (frameworks/minikin/) y he identificado seis archivos clave que contienen vulnerabilidades de diseño.

Archivo Año Problema principal Función vulnerable
FontFamily.cpp 2013 Punteros nulos en getClosestMatch getClosestMatch()
Layout.cpp 2013 Procesa directamente textos largos sin caché doLayoutWord()
OptimalLineBreaker.cpp 2015 Knuth-Plass O(n²) sin límites computeBreaks()
GreedyLineBreaker.cpp 2017 Algoritmo voraz O(n²) en el peor caso processLineBreak()
LineBreaker.cpp 2018 Decisión entre algoritmos sin validar longitud breakIntoLines()
LayoutCache.h 2018 Caché sin límite de tamaño por entrada getOrCreate()

4. Funciones vulnerables y código exacto

4.1 LineBreaker::breakIntoLines — Sin validación de longitud

Archivo: frameworks/minikin/libs/minikin/LineBreaker.cpp (2018)

LineBreakResult breakIntoLines(const U16StringPiece& textBuffer, BreakStrategy strategy,
                               HyphenationFrequency frequency, bool justified,
                               const MeasuredText& measuredText, const LineWidth& lineWidth,
                               const TabStops& tabStops, bool useBoundsForWidth) {
    if (strategy == BreakStrategy::Greedy || textBuffer.hasChar(CHAR_TAB)) {
        return breakLineGreedy(textBuffer, measuredText, lineWidth, tabStops,
                               frequency != HyphenationFrequency::None, useBoundsForWidth);
    } else {
        return breakLineOptimal(textBuffer, measuredText, lineWidth, strategy, frequency, justified,
                                useBoundsForWidth);
    }
}

🔴 Vulnerabilidad: No hay comprobación de textBuffer.size(). Si el texto es de 70KB, se ejecuta breakLineOptimal o breakLineGreedy sin límite.

4.2 OptimalLineBreaker::computeBreaks — O(n²) sin límites

Archivo: frameworks/minikin/libs/minikin/OptimalLineBreaker.cpp (2015)

LineBreakResult LineBreakOptimizer::computeBreaks(const OptimizeContext& context,
                                                  const U16StringPiece& textBuf,
                                                  const MeasuredText& measuredText,
                                                  const LineWidth& lineWidth,
                                                  BreakStrategy strategy, bool justified,
                                                  bool useBoundsForWidth) {
    // ... algoritmo de programación dinámica Knuth-Plass
    // SIN validación de longitud de entrada
    // Complejidad O(n²) en el peor caso
}

🔴 Vulnerabilidad: El algoritmo Knuth-Plass tiene complejidad O(n²). Para 70.000 caracteres, son ~4.9 mil millones de operaciones.

4.3 GreedyLineBreaker::processLineBreak — Bucles anidados

Archivo: frameworks/minikin/libs/minikin/GreedyLineBreaker.cpp (2017)

void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker,
                                         bool doHyphenation) {
    while (isWidthExceeded() || overhangExceedLineLimit(Range(getPrevLineBreakOffset(), offset))) {
        if (tryLineBreakWithWordBreak()) {
            continue;  // El bucle puede ejecutarse muchas veces
        }
        if (doHyphenation && tryLineBreakWithHyphenation(...)) {
            // ...
        }
    }
}

🔴 Vulnerabilidad: Bucle while con llamadas a funciones que recorren el texto. Complejidad O(n²) en el peor caso.

4.4 LayoutCache::getOrCreate — Procesamiento directo sin caché

Archivo: frameworks/minikin/include/minikin/LayoutCache.h (2018)

template <typename F>
void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
                 bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                 bool boundsCalculation, F& f) {
    LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen);
    if (range.getLength() >= CHAR_LIMIT_FOR_CACHE) {
        // ¡PROCESAMIENTO DIRECTO! SIN CACHÉ → BLOQUEO UI
        LayoutPiece piece(text, range, dir, paint, startHyphen, endHyphen);
        // ...
        return;
    }
    // ...
}

🔴 Vulnerabilidad: Los textos largos no se almacenan en caché. Se procesan desde cero cada vez que se renderizan.


5. El pipeline completo: 5 capas de fragilidad

1. LineBreaker.cpp (2018) — breakIntoLines()
   └── Decide entre Greedy y Optimal SIN validar longitud
         │
2. OptimalLineBreaker.cpp (2015) / GreedyLineBreaker.cpp (2017)
   └── Algoritmo O(n²) SIN límites de entrada
         │
3. Layout.cpp (2013) — doLayoutWord()
   └── Llama a LayoutCache SIN verificar tamaño
         │
4. LayoutCache.h (2018) — getOrCreate()
   └── Si texto > CHAR_LIMIT_FOR_CACHE → procesa DIRECTAMENTE
         │
5. HarfBuzz (hb_shape)
   └── Renderizado de glifos → O(n²) o peor
         │
         ▼
   UI BLOQUEADA (5-16 segundos)

🔴 Todas las capas son vulnerables. Ninguna valida la longitud del texto de entrada.


6. FontFamily.cpp: el origen del SIGSEGV en Samsung

El archivo FontFamily.cpp (creado en 2013) contiene la función getClosestMatch, responsable del SIGSEGV documentado en issue #188985643 (2021, Samsung Galaxy J6+).

FakedFont FontFamily::getClosestMatch(FontStyle style, const VariationSettings& axes) const {
    if (features::typeface_redesign_readonly()) {
        int bestIndex = 0;
        Font* bestFont = mFonts[bestIndex].get();  // ← ¡POSIBLE PUNTERO NULO!
        // ...
    }
    // ...
}

🔴 Problema: mFonts puede estar vacío. En producción, MINIKIN_ASSERT no se activa y el programa accede a memoria inválida → SIGSEGV.

Google respondió:

"I haven't receive any crash report at this function on Pixel devices, and likely it is not actionable to me only with this stack trace."


7. LayoutCache.h: el amplificador silencioso

El archivo LayoutCache.h (creado en 2018) es donde la vulnerabilidad se vuelve persistente.

if (range.getLength() >= CHAR_LIMIT_FOR_CACHE) {
    LayoutPiece piece(text, range, dir, paint, startHyphen, endHyphen);
    // PROCESAMIENTO DIRECTO → SIN CACHÉ → BLOQUEO UI
    return;
}

🔴 Esto significa que:

  1. Los textos largos no se almacenan en caché.
  2. Cada vez que se renderizan, se procesan desde cero.
  3. El bloqueo de UI ocurre cada vez que se muestra el texto.

8. Evidencia forense: los números no mienten

Longitud del texto Iteraciones aprox. Tiempo de bloqueo
1.000 caracteres ~1.000² = 1.000.000 < 100 ms
10.000 caracteres ~10.000² = 100.000.000 ~500 ms - 1 s
70.000 caracteres ~70.000² = 4.900.000.000 5 - 16 segundos

🔴 Un texto de 70KB genera casi 5 mil millones de operaciones en el hilo UI.


9. La cronología de la negligencia (2013–2026)

Año Evento Lo que demuestra
2013 FontFamily.cpp y Layout.cpp creados El código vulnerable existe desde hace 11 años
2016 CVE-2016-2414 — DoS en Minikin Google sabía que Minikin era vulnerable y lo arregló
2020 Issue #161830416 — crash en LayoutCache Google recibió evidencia, la ignoró
2021 Issue #188985643 — SIGSEGV en Samsung Google lo ignoró porque no se reproducía en Pixel
2023 Chromium 1447465 — ANR en Chrome reportado por Samsung Google lo cerró con "who knows"
2026 Reporte VRP — libminikin ANR y crash loop Google lo cerró "out of scope"
Jul 2026 Boletín de seguridad sin parche Google sigue ignorando el problema

🔴 Google ha tenido más de 10 años para arreglar esto. Ha arreglado los casos triviales e ignorado los complejos.


10. La contradicción del VRP: $250 y "out of scope"

El timeline:

  • 20 de enero de 2026: Reporte formal al Android VRP (27 vectores).
  • Google paga $250 por el caso Binder/IPC (A-477279924).
  • 15 de junio de 2026: Google cierra el reporte específico de libminikin como "out of scope" (524288518).
  • 16 de junio de 2026: Apelación enviada para STA-015-DL (CVSS 8.6).
  • 6 de julio de 2026: Boletín de seguridad de julio — sin parche para ningún vector.

💡 La contradicción: Google pagó una recompensa por la investigación, pero cerró el reporte más grave como "out of scope". Si está fuera de alcance, ¿por qué pagan?


11. Conclusión: 10 años de código vulnerable

El análisis del código fuente de AOSP confirma que la vulnerabilidad en libminikin no es un bug aislado, sino un fallo de diseño sistémico presente en el núcleo de Android desde 2013.

🔴 Lo que sabemos ahora:

  • 6 archivos vulnerables en AOSP.
  • 5 capas del pipeline de texto sin validación.
  • 10 años de evidencia documentada (2013 → 2026).
  • Google ha tenido más de 10 años para arreglar esto.
  • Ha arreglado los casos triviales (CVE-2016-2414) e ignorado los complejos.

El 30 de julio de 2026 publicaré el whitepaper completo con los 31 vectores documentados, las trazas nativas completas y el análisis forense del código fuente. Si tu aplicación usa TextView, ya estás avisado.

📌 Para desarrolladores (mitigación inmediata):

private static final int MAX_SAFE_LENGTH = 8192;
String safe = text.length() > MAX_SAFE_LENGTH 
    ? text.substring(0, MAX_SAFE_LENGTH) + "…" 
    : text;
textView.setText(safe);

12. Referencias y enlaces

Artículos del investigador

Issues públicos de Google

Código fuente de AOSP


Manuel Garcia Peña (Lostmon) · lostmon@gmail.com · lostmon.blogspot.com
Whitepaper completo: 30 de julio de 2026
 

Browse

About:Me

My blog:http://lostmon.blogspot.com
Mail:Lostmon@gmail.com
Lostmon Google group
Lostmon@googlegroups.com

La curiosidad es lo que hace
mover la mente...

Friends