📌 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 evidencia 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
- Resumen ejecutivo
- Issues públicos de Google (2020–2026)
- Análisis del código fuente: 6 archivos vulnerables
- Funciones vulnerables y código exacto
- El pipeline completo: 5 capas de fragilidad
- FontFamily.cpp: el origen del SIGSEGV en Samsung
- LayoutCache.h: el amplificador silencioso
- Evidencia forense: los números no mienten
- La cronología de la negligencia (2013–2026)
- La contradicción del VRP: $250 y "out of scope"
- Conclusión: 10 años de código vulnerable
- Referencias y enlaces
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:
- Los textos largos no se almacenan en caché.
- Cada vez que se renderizan, se procesan desde cero.
- 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
- "Algorithmic DoS en libminikin.so – Cómo un texto de 70 KB puede congelar casi cualquier app Android" (24 de junio de 2026)
- Blog de Lostmon
Issues públicos de Google
- #161830416 — crash en LayoutCache (2020)
- #167014931 — crash por Float.POSITIVE_INFINITY (2020)
- #188985643 — SIGSEGV en Samsung (2021)
- #40268980 — ANR en Chrome reportado por Samsung (2023)
- #477202817 — Reporte Chrome VRP (2026)
- #531319203 — system_server boot loop (julio 2026)
Código fuente de AOSP
- frameworks/minikin/ — Repositorio de Minikin
- LineBreaker.cpp
- OptimalLineBreaker.cpp
- GreedyLineBreaker.cpp
- Layout.cpp
- LayoutCache.h
- FontFamily.cpp
Whitepaper completo: 30 de julio de 2026