Desafiar Android desde el sofá y un smartphone

Friday, July 17, 2026

¿Se puede desafiar la seguridad de Android usando SOLO un teléfono móvil?

La respuesta es sí.
Y esta es mi historia.


Soy Manuel García Peña (Lostmon), investigador independiente de seguridad y presidente de BojosXtu, una asociación de apoyo y empoderamiento en salud mental.

Durante los últimos cuatro años he perseguido un mismo patrón dentro de Android. Lo que comenzó como una intuición técnica terminó convirtiéndose en una investigación donde documento 31 vectores de ataque relacionados con un mismo fenómeno al que he llamado Structured Text Amplification (STA), un patrón que implica componentes críticos de Android como libminikin, SystemUI, el pipeline de renderizado de texto y distintos mecanismos internos de procesamiento y restauración del estado del sistema.

📅 La investigación completa verá la luz el próximo 30 de julio.

Pero lo más importante no es el hallazgo técnico.
Lo importante es cómo llegué hasta él.


🧒 Antes del diagnóstico: perseguir bugs era mi forma de entender el mundo

Mucho antes de recibir un diagnóstico de salud mental, ya era el típico chaval que desmontaba todo para descubrir cómo funcionaba.

Mientras otros jugaban, yo abría aplicaciones, observaba comportamientos extraños y me hacía siempre la misma pregunta:

"¿Y si esto se puede romper?"

No sabía lo que era un Bug Bounty. No sabía lo que era AOSP. Ni siquiera imaginaba que algún día investigaría vulnerabilidades.

Simplemente tenía una curiosidad casi obsesiva por comprender cómo funcionaban los sistemas.

Con los años empecé a encontrar fallos reales, a reportarlos y a aprender de forma completamente autodidacta.

Antes de tener que retirarme por problemas de salud mental, llegué a descubrir y documentar, más de 400 vulnerabilidades.


🖤 El momento en que tuve que dejarlo

Después llegó el diagnóstico.

Y con él, una realidad que me costó mucho aceptar.

Ya no podía pasar horas delante de un ordenador como antes.

Mi mente empezó a jugarme malas pasadas. Cuando llega. los Fantasmas, cada uno debe librar sus batallas con sus propios demonios.

Cuanto más tiempo permanecía frente a una pantalla grande, más me absorbía el mundo que había dentro de ella. Perdía la noción del tiempo, perdía el entorno y, a veces, sentía que me perdía yo mismo entre miles de líneas de código.

Tuve que dejar de investigar y alejarme de la tecnología que tanto me apasiona. ¿Como borras algo de tu vida que ha estado siempre?

Y fue durísimo.

Porque no estaba perdiendo simplemente un hobby.
Sentía que estaba perdiendo una parte de mí.

Pensé que todo había terminado.


📱 Mi laboratorio: un Xiaomi Redmi 14 5G

Pero la curiosidad seguía ahí.

Y encontré otra forma de poder de vivir, sin estar rodeado de tanta tecnología.

Gran parte de esta investigación se ha realizado desde un Xiaomi Redmi 14 5G.

Desde el sofá.

En mis días buenos.
Cuando la cabeza me dejaba concentrarme un rato.

Sin un laboratorio.
Sin una estación de trabajo.
Sin varios monitores.
Sin ADB.
Sin Android Studio.

Solo una pantalla pequeña, AOSP abierto en el navegador y miles de líneas de código leídas función a función, siguiendo llamadas entre clases para intentar comprender cómo pensaba Android.

Durante cuatro años fui reconstruyendo ese enorme puzle.

Había días en los que solo podía revisar unas pocas funciones.

Otros encontraba una pista y pasaba horas siguiendo llamadas entre TextView, StaticLayout, LineBreaker, libminikin, SystemUI y muchos otros componentes del framework.

Así comenzó la persecución de un patrón que aparecía una y otra vez.


🔍 Cuatro años persiguiendo el mismo fantasma

La primera vez que observé un comportamiento extraño fue hace años, en Android 9.

Lo reproduje.
Tomé notas.
Y seguí investigando.

Con el tiempo aparecieron nuevos síntomas:

  • • bloqueos en navegadores;
  • ANRs provocados por determinadas estructuras de texto;
  • • problemas durante la impresión;
  • • corrupción de estado en aplicaciones;
  • crash loops persistentes de SystemUI;
  • • comportamientos aparentemente distintos que, una y otra vez, me llevaban al mismo lugar.

La mayoría habría pensado que eran errores independientes.

Yo no conseguía quitarme de la cabeza la sensación de que todos compartían un mismo origen.

Cada vez que encontraba un síntoma nuevo volvía al mismo sitio del código.

Fue entonces cuando empecé a sospechar que no estaba persiguiendo treinta y un errores distintos.
Estaba persiguiendo un único patrón que se manifestaba de formas diferentes.


🧠 La neurodivergencia también puede aportar otra forma de mirar

Durante mucho tiempo pensé que mi diagnóstico era un obstáculo para investigar.

Hoy lo veo de otra manera.

Con el tiempo comprendí que mi forma de procesar la información también tenía fortalezas: una enorme capacidad para detectar patrones, mantener el foco durante semanas sobre un mismo problema y conectar detalles que parecían no tener relación.

No romantizo la salud mental.
Hay días malos.
Hay agotamiento.
Hay frustración.
Hay momentos en los que la cabeza simplemente no coopera.

Pero también he aprendido que cada persona observa el mundo de una manera distinta.

Y, a veces, esa mirada diferente permite encontrar conexiones que habían pasado desapercibidas.


🤝 La comunidad hizo posible continuar

Yo solo no habría podido validar todo esto.

Compañeros y amigos me prestaron dispositivos para reproducir los distintos vectores en fabricantes y versiones diferentes de Android.

Gracias a ellos pude comprobar comportamientos en Xiaomi, Samsung, OPPO, OnePlus y otros terminales.

La investigación independiente también se construye así.

Con personas que comparten tiempo, hardware y confianza sin esperar nada a cambio.


💰 Y un día llegaron dos mensajes que jamás olvidaré

En medio de todo este proceso llegaron dos validaciones que nunca habría imaginado cuando empecé leyendo AOSP desde la pantalla de un teléfono móvil.

Google confirmó una vulnerabilidad relacionada con Binder y me concedió una recompensa a través de su programa de vulnerabilidades.

Y hace apenas unas horas Xiaomi confirmó otra vulnerabilidad y me concedió una recompensa mediante HackerOne.

No es una cuestión de dinero.

Lo verdaderamente importante fue leer una frase muy sencilla:

"This vulnerability is confirmed."

Después de tantos años investigando en silencio, esa frase significó mucho más que una recompensa económica.

Fue la confirmación de que todo aquel esfuerzo, todas aquellas horas leyendo código y persiguiendo un patrón que casi nadie veía, estaban dando fruto.


🚀 La investigación continúa

El próximo 30 de julio publicaré el whitepaper completo con la metodología, el marco teórico de Structured Text Amplification (STA), los 31 vectores documentados y todas las evidencias recogidas durante estos años.

No sé cuál será su impacto.
No sé cuántas personas lo leerán.

Pero sí sé una cosa.

La curiosidad no depende del hardware.

No depende de tener el mejor laboratorio.
Ni de disponer de cuatro monitores.

Depende de seguir haciéndote preguntas.

Depende de no dejar de preguntarte:

"¿Y si nadie ha mirado aquí?"

Porque, al final, investigar nunca ha consistido en tener el mejor equipo.

Ha consistido en no perder nunca la capacidad de hacerse preguntas.

Y después de todo este tiempo sigo creyendo exactamente lo mismo que cuando era un chaval persiguiendo bugs sin saber siquiera cómo se llamaban.

"La curiosidad es lo que hace mover la mente." 🛡️

La investigación continúa.
Y yo también.


#Lostmon #Android #AOSP #Ciberseguridad #MobileSecurity #SystemUI #libminikin #STA #StructuredTextAmplification #BugBounty #GoogleVRP #Xiaomi #HackerOne #SaludMental #InvestigaciónIndependiente #BojosXtu
Manuel García Peña (Lostmon)
lostmon.blogspot.com · lostmon@gmail.com

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

📌 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. 10 ARCHIVOS. MAS DE 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.

  • 10 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

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
}

Pero hay un matiz que el código revela: hay una poda activa (active = j + 1) y una optimización bestHope explícitamente comentada como tal: Cpp Esto significa que el algoritmo no es un O(n²) puro sin ninguna mitigación — tiene un mecanismo de poda que en la práctica reduce trabajo cuando delta mayor que 0 (la línea se desborda), avanzando el puntero active. El peor caso teórico sigue siendo O(n²) (cuando casi todos los candidatos permanecen "activos" simultáneamente, como pasaría con una URL sin espacios donde casi cualquier punto de ruptura es candidato).

if (jScore + bestHope >= best) continue;

🔴 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.


🧠 El Pipeline de Amplificación de Minikin: Anatomía de un Fallo de Diseño de 13 Años

Aquí profundizo en el pipeline completo y en cómo cada capa amplifica el payload hasta convertir 70KB en un bloqueo de 16 segundos.


El motor de layout de texto de Android (Minikin) contiene un fallo de diseño sistémico que permite que un texto de tan solo 70.000 caracteres (aproximadamente 70KB) bloquee el hilo de interfaz de usuario durante 5-16 segundos, provocando ANR en cualquier aplicación que muestre texto sin truncar.

El problema no reside en una única función, sino en más de 10 archivos y 15 funciones que conforman el pipeline de procesamiento de texto. Cada capa del pipeline amplifica el coste computacional sin aplicar ningún tipo de validación de longitud o límite de entrada.

🔴 Dato clave: El código más antiguo data de 2013. Google ha tenido más de 13 años para arreglar esto.


🧠 El esquema de amplificación: 10 capas de fragilidad

Un texto de 70KB con una estructura específica (/code> repetido) activa una cascada de amplificación en la que cada capa del pipeline multiplica el coste computacional:

70KB de entrada
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 1: LineBreaker.cpp (2018)                              │
 │ breakIntoLines() decide entre Greedy y Optimal             │
 │ SIN validar longitud → pasa 70KB al siguiente paso          │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 2: OptimalLineBreaker.cpp (2015) / GreedyLineBreaker  │
 │ computeBreaks() / processLineBreak()                       │
 │ Algoritmo O(n²) → 70.000² = 4.900.000.000 iteraciones      │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 3: Layout.cpp (2013)                                   │
 │ doLayoutWord() llama a LayoutCache SIN verificar tamaño    │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 4: LayoutCache.h (2018)                                │
 │ getOrCreate(): si texto > CHAR_LIMIT_FOR_CACHE             │
 │ → procesa DIRECTAMENTE, sin caché                         │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 5: LayoutCore.cpp (2018)                               │
 │ LayoutPiece() constructor → hb_shape (HarfBuzz)            │
 │ HarfBuzz tiene complejidad O(n²) o peor                    │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 6: Hyphenator.cpp (2015)                               │
 │ hyphenateFromCodes() → bucles anidados O(n²)               │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 7: Measurement.cpp (2015)                              │
 │ getOffsetForAdvance() / distributeAdvances() → O(n²)       │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 8: WordBreaker.cpp (2015)                              │
 │ detectEmailOrUrl() / findNextBreakInEmailOrUrl() → O(n)   │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 9: FontCollection.cpp (2013)                           │
 │ getGlyphScore() → llama a hb_shape() nuevamente           │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────────────────────┐
 │ CAPA 10: FontFamily.cpp (2013)                              │
 │ getClosestMatch() → punteros nulos → SIGSEGV               │
 └─────────────────────────────────────────────────────────────┘
      │
      ▼
   🔥 UI BLOQUEADA (5-16 segundos)

🔴 10 capas. 10 archivos. Ninguna valida la longitud del texto de entrada.


📂 Tabla completa de archivos y funciones vulnerables

Archivo Año Función Complejidad Problema
LineBreaker.cpp2018breakIntoLines()O(1)No valida longitud de entrada
OptimalLineBreaker.cpp2015computeBreaks()O(n²)Knuth-Plass sin límites
GreedyLineBreaker.cpp2017processLineBreak()O(n²)Bucles anidados sin límites
Layout.cpp2013doLayoutWord()O(n)Llama a LayoutCache sin verificar tamaño
LayoutCache.h2018getOrCreate()O(n)CHAR_LIMIT_FOR_CACHE → procesa directamente
LayoutCore.cpp2018LayoutPiece()O(n²)Llama a hb_shape (HarfBuzz) sin límites
Hyphenator.cpp2015hyphenateFromCodes()O(n²)Bucles anidados sin límites
Measurement.cpp2015getOffsetForAdvance()O(n²)Bucles anidados sin límites
Measurement.cpp2015distributeAdvances()O(n²)Bucles anidados sin límites
WordBreaker.cpp2015detectEmailOrUrl()O(n)Recorre texto sin límites
WordBreaker.cpp2015findNextBreakInEmailOrUrl()O(n)Recorre texto sin límites
FontCollection.cpp2013getGlyphScore()O(n²)Llama a hb_shape sin límites
FontCollection.cpp2013itemize()O(n)Recorre texto sin límites
FontFamily.cpp2013getClosestMatch()O(n)Puntero nulo → SIGSEGV

📊 10 archivos. 15 funciones. 10 años de código vulnerable. 13 años desde el primer archivo.


🔬 Amplificación paso a paso: Cómo 70KB pieden convertirse en 5 mil millones de operaciones

Paso 1: Entrada (70KB)

El payload es una URL de 70.000 caracteres con el patrón [PAYLOAD] repetido:

https://example.com/[PAYLOAD]

Paso 2: LineBreaker::breakIntoLines (2018)

if (strategy == BreakStrategy::Greedy || textBuffer.hasChar(CHAR_TAB)) {
    return breakLineGreedy(...);   // ← No hay validación de longitud
} else {
    return breakLineOptimal(...);  // ← Tampoco hay validación
}

Amplificación: Decide el algoritmo sin comprobar la longitud del texto.

Paso 3: OptimalLineBreaker::computeBreaks (2015) — O(n²)

LineBreakResult LineBreakOptimizer::computeBreaks(...) {
    // ... algoritmo de programación dinámica Knuth-Plass
    // SIN validación de longitud de entrada
    // Complejidad O(n²) en el peor caso
}

Amplificación: Para n=70.000 → 4.900.000.000 iteraciones.

Paso 4: Layout::doLayoutWord (2013)

float Layout::doLayoutWord(...) {
    // ...
    LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
                                           boundsCalculation, f);
    // ...
}

Amplificación: Llama a LayoutCache sin verificar el tamaño del texto.

Paso 5: LayoutCache::getOrCreate (2018)

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

Amplificación: Los textos largos no se almacenan en caché. Se procesan desde cero cada vez.

Paso 6: LayoutPiece constructor (2018)

LayoutPiece::LayoutPiece(...) {
    // ...
    hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
             features.size());
    // ...
}

Amplificación: hb_shape (HarfBuzz) puede tener complejidad O(n²) o peor.

Paso 7: Hyphenator::hyphenateFromCodes (2015)

void HyphenatorCXX::hyphenateFromCodes(...) const {
    for (size_t i = 0; i < len - 1; i++) {
        for (size_t j = i; j < len; j++) {
            // BUCLES ANIDADOS SIN LÍMITES
        }
    }
}

Amplificación: Bucles anidados O(n²) que se ejecutan para cada palabra.

Paso 8: Measurement::getOffsetForAdvance / distributeAdvances (2015)

size_t getOffsetForAdvance(...) {
    for (size_t i = start; i < max; i++) { /* ... */ }
    for (size_t i = searchStart; i <= max; i++) { /* ... */ }
}

Amplificación: Bucles anidados O(n²) en el peor caso.

Paso 9: WordBreaker::detectEmailOrUrl (2015)

void WordBreaker::detectEmailOrUrl() {
    for (i = mLast; i < mTextSize; i++) { /* ... */ }
}

Amplificación: Bucle O(n) que recorre el texto sin límites.

Paso 10: FontCollection::getGlyphScore (2013)

uint32_t getGlyphScore(...) {
    hb_shape(font.get(), buffer.get(), nullptr, 0);  // O(n²)
}

Amplificación: Llama a hb_shape para cada fuente evaluada.

🔴 Resultado: 70KB → 5 mil millones de iteraciones → 16 segundos de bloqueo → ANR.


⚡ El payload mínimo

Para activar todo el pipeline de amplificación, se necesita un texto con:

  • Longitud: > 70.000 caracteres
  • Estructura: Caracteres que multipliquen los puntos de decisión:
    • # → duplica decisiones de salto
    • / → cada barra añade un punto de ruptura
    • % → expansión UTF-8 → UTF-16
    • → propiedades de salto ambiguas

Payload óptimo:

[PAYLOAD]

Repetido hasta alcanzar 70.000 caracteres.


📅 Cronología (2013–2026)

Año Evento Lo que demuestra
2013FontCollection.cpp, FontFamily.cpp, Layout.cpp creadosEl código vulnerable existe desde hace 13 años
2015OptimalLineBreaker.cpp, Hyphenator.cpp, Measurement.cpp, WordBreaker.cpp creadosEl pipeline de amplificación se completa
2016CVE-2016-2414 — DoS en MinikinGoogle sabía que Minikin era vulnerable y lo arregló
2017GreedyLineBreaker.cpp creadoSe añade una capa más de amplificación
2018LineBreaker.cpp, LayoutCore.cpp, LayoutCache.h creadosEl pipeline alcanza 10 capas
2020Issue #161830416 — crash en LayoutCacheGoogle recibió evidencia, la ignoró
2021Issue #188985643 — SIGSEGV en SamsungGoogle lo ignoró porque no se reproducía en Pixel
2023Chromium 1447465 — ANR en Chrome reportado por SamsungGoogle lo cerró con "who knows"
2026Reporte VRP — 31 vectores documentadosGoogle lo cerró "out of scope"
2026Boletín de julio — sin parcheGoogle sigue ignorando el problema

🔴 13 años de código vulnerable. 10 archivos. 15 funciones. Google no lo ha arreglado.


El pipeline de layout de texto de Android está compuesto por más de 10 archivos y 15 funciones que, en conjunto, forman un sistema de amplificación de DoS. Cada capa multiplica el coste computacional sin aplicar validación de longitud, permitiendo que un texto de 70KB bloquee la UI durante 5-16 segundos.

Google ha tenido 13 años para arreglar esto. Los archivos vulnerables datan de 2013 (FontCollection.cpp, FontFamily.cpp, Layout.cpp) y se han ido añadiendo más capas a lo largo de los años (2015, 2017, 2018). Ninguna de ellas ha sido parcheada para abordar el problema de la validación 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 libminikin (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) Google cierra el reporte de apelación específico de libminikin como "NoT reproducible"
  • 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?

Por primera vez, después de más de 400 vulnerabilidades descubiertas y documentadas a lo largo de estos años, he sido recompensado con 250 dólares por la primera parte de mi trabajo. Ni la cantidad, ni la forma en que se ha gestionado la segunda parte de la investigación reflejan el alcance ni la gravedad de las vulnerabilidades documentadas

Matemáticamente, teniendo en cuenta que pueden ser 2.500 millones de dispositivos afectados, eso es 0,0000001 dólares por dispositivo. Es decir, menos de una diezmilésima parte de un céntimo por cada teléfono vulnerable.


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

🔥 1.6 Minikin es un síntoma de una brecha de resiliencia sistémica

La vulnerabilidad de libminikin no es un bug aislado. Es un síntoma de una brecha de resiliencia sistémica en el framework de Android: la ausencia de un mecanismo estandarizado de "safe fallback" para manejar datos que superan los límites del sistema.

El mismo patrón aparece en al menos diez componentes distintos del sistema operativo Android:

Componente Modo de fallo Impacto
FragmentManager No captura TransactionTooLargeException Bucle de crash permanente
TaskPersister Persiste el estado corrupto en disco El bucle de crash sobrevive a reinicios
SystemUI Lee TaskPersister sin validación Bucle de crash → pantalla de bloqueo
Print Service Sin validación del tamaño del documento SystemUI crash
ClipboardManager Serializa todo el contenido del portapapeles sin límites SystemUI crash
Binder Límite de 1MB sin safe fallback Crash de la app
libminikin.so Sin validación de longitud de entrada ANR (5–16s)
LayoutCache CHAR_LIMIT_FOR_CACHE fuerza el procesamiento directo Amplifica el DoS
BitmapCache Sin validación del tamaño de imagen (issue #531319203) Crash de system_server → bucle de arranque
MediaSession Sin validación del tamaño de la carátula Crash de system_server → bucle de arranque

🔴 Diez componentes. Diez modos de fallo diferentes. El mismo patrón subyacente: sin validación de entrada antes del procesamiento, y sin safe fallback cuando se superan los límites.

Esto no es un bug de Minikin. Es un fallo de diseño del framework de Android que ha estado presente durante más de una década, afectando a componentes que van desde la capa de aplicación hasta el servidor central del sistema. Google ha añadido capas de complejidad a Minikin sin abordar la causa raíz, y ha descartado los informes de estos problemas como "fuera de alcance" o "comportamiento intencionado" durante años.

Minikin no es la enfermedad. Es un síntoma.



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

Algorithmic DoS en libminikin.so

Wednesday, June 24, 2026
Algorithmic DoS en libminikin.so – Cómo un texto de 70 KB puede congelar casi cualquier app Android 
 

Un texto de 70.000 caracteres puede congelar casi cualquier app de  Android durante 10–16 segundos. No es corrupción de memoria. Es un ataque algorítmico que explota la complejidad O(n²) del motor de diseño de texto nativo de Android. Y lo peor: cualquier aplicación con un TextView es vulnerable.

A diferencia de muchas vulnerabilidades que requieren aplicaciones de prueba específicas para su reproducción, varios de los vectores documentados pueden activarse simplemente mediante enlaces especialmente construidos que son procesados por componentes estándar del sistema Android. Esto amplía significativamente la superficie de exposición potencial

He documentado 31 vectores en 9 empresas, incluyendo Google, Meta, Microsoft, Mozilla, Opera, DuckDuckGo, Brave, Tor Project y Xiaomi. El fallo reside en libminikin.so, el motor de diseño de texto de Android, y afecta a todos los dispositivos con Android 9 a 16.


1. El problema: Algoritmos que se vuelven en tu contra

libminikin.so implementa el algoritmo de salto de línea óptimo (Knuth-Plass) en la función LineBreakOptimizer::computeBreaks. Este algoritmo tiene complejidad O(n²) en el peor caso: si duplicas la longitud del texto, el tiempo de procesamiento se multiplica por cuatro.

Para un texto normal de 200 caracteres, el tiempo es imperceptible. Pero para uno de 70.000 caracteres con la estructura adecuada, el algoritmo ejecuta millones de operaciones y bloquea el hilo de la interfaz durante más de 10 segundos.

El vector documentado en libminikin no requiere una aplicación de demostración específica. En los escenarios analizados, basta con que un componente del sistema represente una URL especialmente construida en un TextView para activar el cálculo intensivo del algoritmo de composición de texto.

📌 Idea clave: No es la longitud, es la estructura. Una URL con caracteres como #, /, % y multiplica el número de "candidatos a salto de línea", y el algoritmo se dispara.

2. La estructura del ataque: Caracteres que multiplican el caos

Carácter Función en la URL Efecto en libminikin
# Fragmento (separador lógico) Duplica las decisiones de salto de línea
/ Separador de rutas Cada barra es un punto de ruptura → O(n²) con el número de barras
% Codificación porcentual Expansión UTF-8 → UTF-16, fragmenta la cadena
Carácter multibyte Propiedades de salto de línea ambiguas → el algoritmo explora ambas opciones

El patrón óptimo repite miles de veces. Cada 9 bytes generan múltiples puntos de decisión y el algoritmo explota.

Resultado: La UI se congela durante 10–16 segundos. El stack trace nativo confirma que libminikin es el único responsable.

4. Vectores de ataque: Más allá de los navegadores

El fallo no se limita a los navegadores. Cualquier aplicación que muestre texto en un TextView sin truncar es un vector de ataque:

Componente / Aplicación ¿Muestra URL larga? Vulnerabilidad observada
Chrome (menú contextual) ✅ ANR de 5+ segundos
Firefox (diálogo externo) ✅ ANR de 16 segundos (enlace real de Google Drive)
Opera / Opera Mini ✅ Crash con TransactionTooLargeException
DuckDuckGo Sí (historial) ✅ Bucle de cierre permanente
Google drive ⚠️ Potencial
WhatsApp (vista previa de enlace) ⚠️ Potencial
Cualquier app con TextView ✅ Confirmado con PoC

5. El vector crítico: STA-015-DL (SystemUI crash loop)

🔥 CVSS 8.6 (CRÍTICO) – Un solo clic en un enlace de Google Drive puede provocar un bucle de cierre de SystemUI que requiere reinicio forzado.

La cadena de ataque STA-015-DL demuestra que el problema trasciende las aplicaciones y afecta a SystemUI, la interfaz del sistema:

  1. La víctima hace clic en un archivo HTML alojado en Google Drive.
  2. Google Drive lanza un Intent.ACTION_VIEW con una URL malformada.
  3. La URL bypasea todas las validaciones del navegador porque llega a través de callingPackage=com.google.android.apps.docs (certificado por Android).
  4. La URL corrompe el estado de TaskPersister en disco.
  5. SystemUI intenta leer el estado → DeadObjectExceptionbucle de cierre.
  6. Recuperación: reinicio forzado del dispositivo.

Evidencia de campo: Un dispositivo en producción (Xiaomi Redmi Note 14 5G, Android 16) registró 85 SystemUI crashes en 4 días, con un tiempo mínimo de supervivencia de 374ms.

// SystemUI crash en el arranque – bugreport 2026-05-26 // Process-Runtime: 374ms – crashea antes de renderizar cualquier UI android.os.BadParcelableException: Failure retrieving array; only received 1 of 4 at android.content.pm.BaseParceledListSlice.<init>(...) at com.android.wm.shell.sysui.ShellInit.init(...) ← crash en el arranque Caused by: android.os.DeadObjectException: Transaction failed on small parcel

6. Stack traces nativos (evidencia forense)

Chrome – Menú contextual (24 de mayo de 2026)

// ANR — Chrome PID 3533 "main" prio=5 tid=1 Native ← UI THREAD BLOCKED native: minikin::getPrevWordBreakForCache libminikin.so native: minikin::LayoutCacheKey::LayoutCacheKey libminikin.so native: minikin::LayoutCache::getOrCreate libminikin.so native: minikin::StyleRun::getLineMetrics libminikin.so native: minikin::MeasuredText::getLineMetrics libminikin.so native: minikin::LineBreakOptimizer::computeBreaks ← O(n²) en URL larga native: minikin::breakLineOptimal libminikin.so native: android::nComputeLineBreaks libhwui.so at android.widget.TextView.onMeasure (TextView.java:11486) at org.chromium.chrome.browser.contextmenu.ContextMenuListView.onMeasure ↑ MENÚ CONTEXTUAL DE CHROME — long-press en URL sobredimensionada

Firefox – Diálogo de aplicación externa (10 de junio de 2026)

// org.mozilla.firefox PID 3506 — ANR 2026-06-10 — 16.006 ms "main" prio=5 tid=1 Native ← UI THREAD BLOCKED 16.006 ms native: minikin::LayoutCacheKey::LayoutCacheKey+120 libminikin.so native: minikin::LayoutCache::getOrCreate libminikin.so native: minikin::LayoutPieces::getOrCreate libminikin.so native: minikin::StyleRun::getLineMetrics libminikin.so native: minikin::MeasuredText::getLineMetrics libminikin.so native: minikin::LineBreakOptimizer::computeBreaks+1752 ← O(n²) en URL larga native: minikin::breakLineOptimal+476 libminikin.so native: android::nComputeLineBreaks+356 libhwui.so at org.mozilla.fenix.customtabs.ExternalAppBrowserActivity URL rendering // Trigger: Enlace REAL de Google.Drive — NO una prueba controlada

7. Mitigación

7.1 A nivel de framework (Google/AOSP)

Google debería implementar un límite de entrada antes de ejecutar el algoritmo O(n²):

// En LineBreakOptimizer.cpp if (textLength > MAX_SAFE_TEXT_LENGTH_FOR_OPTIMIZER) { return computeBreaksGreedy(measured, start, end, constraints); // O(n) }

7.2 A nivel de aplicación (inmediato)

Los desarrolladores pueden truncar el texto antes de mostrarlo en un TextView:

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

8. Conclusión

La complejidad O(n²) en libminikin convierte una URL de 70 KB en un arma de denegación de servicio que puede congelar cualquier app Android que la muestre en un TextView sin truncar. No es corrupción de memoria, es explotación algorítmica — usar la propia complejidad del sistema en su contra.

Google ha empezado a moverse (LargePayloadSupport, savedstate 1.5.0), pero no hay parche público para libminikin a fecha de junio de 2026. La comunidad y los desarrolladores pueden aplicar mitigaciones inmediatas truncando el texto antes de mostrarlo.

Nota sobre el proceso de divulgación Esta vulnerabilidad fue notificada inicialmente al Android Vulnerability Rewards Program (VRP) el 20 de enero de 2026, como parte de una investigación más amplia. El reporte específico sobre la vulnerabilidad de complejidad algorítmica en libminikin.so fue presentado el 15 de junio de 2026. El reporte fue cerrado con la clasificación "Out of Scope". Posteriormente, el 21 de junio de 2026, se presentó una apelación acompañada de evidencia adicional, incluyendo informes ANR, trazas de pila nativas y resultados obtenidos durante la investigación. La apelación fue finalmente cerrada con la clasificación "Not Reproducible". El análisis técnico y la evidencia presentada en este artículo reflejan los resultados obtenidos durante la investigación independiente realizada por el autor y se publican con fines de investigación, documentación y mejora de la seguridad.

Apéndice: Nuevo caso reproducible (28 de junio de 2026) – Google App / Android Translate

Desde la publicación inicial de este análisis, he seguido monitorizando el comportamiento del sistema. El pasado 28 de junio de 2026 apareció un segundo caso, completamente independiente del anterior, que no hace sino reforzar la hipótesis planteada.

Mientras que el primer vector documentado en la sección 4 utilizaba StaticLayout y breakLineOptimal(), este nuevo caso utiliza DynamicLayout y breakLineGreedy() durante el procesamiento de texto iniciado por Android System Intelligence mediante un android.intent.action.TRANSLATE.

Secuencia observada

Usuario selecciona texto
  ▼
Android System Intelligence
  ▼
Google App (ImplicitTranslateSearchEntrypointInternal)
  ▼
SpannableStringBuilder.replace()
  ▼
DynamicLayout.reflow()
  ▼
libminikin::breakLineGreedy()
  ▼
getPrevWordBreakForCache()
  ▼
ANR (Application Not Responding)

Cinco ANR consecutivos

El mismo texto produjo cinco ANR consecutivos en aproximadamente catorce minutos. Todos compartían el mismo patrón:

  • Main thread bloqueado durante más de 6 segundos.
  • Entrada por DynamicLayout.
  • Ejecución dentro de libminikin.
  • Funciones getPrevWordBreakForCache() o getNextWordBreakForCache().

Después de varios ANR, el proceso también comenzó a finalizar con TransactionTooLargeException durante la restauración del estado de la actividad, un efecto secundario posterior al bloqueo del hilo principal que conecta directamente con el fenómeno de amplificación estructural descrito en la sección 5.

Comparación con el caso de Edge

Aspecto Microsoft Edge Google App
Layout StaticLayout DynamicLayout
Algoritmo breakLineOptimal() breakLineGreedy()
Desencadenante Re‑layout tras cambio del sistema Reflow tras modificar texto
Entrada Omnibox Traductor integrado
ANR 1 ANR 5 ANR consecutivos

Aunque las rutas son diferentes, ambas convergen en el mismo componente del framework:

          StaticLayout
              │
              ├──► breakLineOptimal()
              │
          DynamicLayout
              │
              ├──► breakLineGreedy()
              │
              ▼
         libminikin
              │
              ├──► getPrevWordBreakForCache()
              └──► getNextWordBreakForCache()

Implicaciones

Este segundo caso reduce significativamente la probabilidad de que el problema sea específico de una aplicación concreta. Ahora existen al menos dos aplicaciones independientes, con dos flujos de ejecución distintos y dos algoritmos de line breaking diferentes, que terminan bloqueando el hilo principal dentro de libminikin sobre la misma versión de Android 16 (BuildId 4fabe53671b5ead88314c00a1fd6d67d).

En otras palabras, la evidencia ya no apunta únicamente a un problema asociado a Microsoft Edge, sino a un comportamiento común del framework de composición de texto de Android cuando procesa determinadas entradas.

Este segundo caso demuestra que la Amplificación de Texto Estructurado no es un fenómeno limitado a un flujo de ejecución concreto, sino que puede manifestarse en distintos puntos del framework siempre que el texto pase por las capas de composición y medida de libminikin.

— Añadido el 28 de junio de 2026

Análisis interno del pipeline de layout de Minikin

Tras correlacionar los múltiples ANR obtenidos con el código fuente de AOSP, la investigación apunta a que las funciones getPrevWordBreakForCache() y getNextWordBreakForCache() probablemente no constituyen la causa raíz del problema, sino que forman parte de un pipeline de composición de texto mucho más amplio.


DynamicLayout / StaticLayout
            │
            ▼
      MeasuredText
            │
            ▼
     LayoutSplitter
            │
   ┌────────┴────────┐
   ▼                 ▼
getPrevWordBreakForCache()
getNextWordBreakForCache()
            │
            ▼
       LayoutCache
            │
            ▼
       LayoutPiece
            │
            ▼
 WordBreaker / Hyphenation
            │
            ▼
breakLineGreedy() / breakLineOptimal()

El análisis del código fuente de AOSP muestra que LayoutSplitter utiliza getPrevWordBreakForCache() y getNextWordBreakForCache() para determinar los límites del fragmento de texto que será almacenado dentro de LayoutCache.

Además, la implementación de LayoutCache indica que cuando el texto supera un determinado tamaño (CHAR_LIMIT_FOR_CACHE), la caché deja de reutilizarse y el sistema debe reconstruir un nuevo LayoutPiece, recalculando el layout completo.


LayoutCache::getOrCreate()

        │

        ├── Cache hit
        │       │
        │       ▼
        │  Reutiliza LayoutPiece
        │
        └── Cache miss
                │
                ▼
      Construcción de LayoutPiece
                │
                ▼
        Glyph shaping
                │
                ▼
         Word breaking
                │
                ▼
      Cálculo del layout

Esto conduce a una nueva hipótesis de trabajo. Más que encontrarnos ante un fallo localizado en getPrevWordBreakForCache(), es posible que los ANR sean consecuencia de la reconstrucción repetitiva del pipeline completo de composición de texto cuando la reutilización de la caché deja de ser efectiva.

Esta hipótesis encaja con los casos observados durante la investigación:

  • Microsoft Edge alcanza este pipeline mediante StaticLayout y breakLineOptimal().
  • Google Translate lo hace mediante DynamicLayout y breakLineGreedy().
  • Ambas rutas convergen finalmente en la misma infraestructura interna de Minikin encargada de la caché y del cálculo de límites de palabra.

Un aspecto especialmente relevante es que Google ha realizado múltiples modificaciones en esta parte del código fuente de Minikin a lo largo de los últimos años, incluyendo optimizaciones específicas del sistema de caché y del rendimiento del motor de layout. Esto sugiere que se trata de una zona considerada crítica desde el punto de vista del rendimiento.

En el momento de redactar este artículo, esta explicación constituye una hipótesis fundamentada en la correlación entre los stack traces de los ANR y el análisis del código fuente de AOSP. Será necesario un análisis más profundo —mediante perfilado o reproducción controlada— para determinar si la causa raíz corresponde a una invalidación de la caché, un problema de complejidad algorítmica o cualquier otro cuello de botella interno del motor de composición de texto de Minikin.


🔴 Conclusión: Esta evidencia independiente refuerza el argumento de que la vulnerabilidad en libminikin no es un problema específico de navegadores, sino un fallo fundamental del framework de layout de texto de Android que afecta a todo el ecosistema de aplicaciones Android.

El 30 de julio de 2026 publicaré el whitepaper completo con los 31 vectores documentados y la evidencia forense completa. Si tu aplicación usa TextView, ya estás avisado.

Manuel Garcia Peña (Lostmon)
Investigador independiente · lostmon@gmail.com
Blog: lostmon.blogspot.com

 

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