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
馃敟 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.cpp | 2018 | breakIntoLines() | O(1) | No valida longitud de entrada |
| OptimalLineBreaker.cpp | 2015 | computeBreaks() | O(n²) | Knuth-Plass sin l铆mites |
| GreedyLineBreaker.cpp | 2017 | processLineBreak() | O(n²) | Bucles anidados sin l铆mites |
| Layout.cpp | 2013 | doLayoutWord() | O(n) | Llama a LayoutCache sin verificar tama帽o |
| LayoutCache.h | 2018 | getOrCreate() | O(n) | CHAR_LIMIT_FOR_CACHE → procesa directamente |
| LayoutCore.cpp | 2018 | LayoutPiece() | O(n²) | Llama a hb_shape (HarfBuzz) sin l铆mites |
| Hyphenator.cpp | 2015 | hyphenateFromCodes() | O(n²) | Bucles anidados sin l铆mites |
| Measurement.cpp | 2015 | getOffsetForAdvance() | O(n²) | Bucles anidados sin l铆mites |
| Measurement.cpp | 2015 | distributeAdvances() | O(n²) | Bucles anidados sin l铆mites |
| WordBreaker.cpp | 2015 | detectEmailOrUrl() | O(n) | Recorre texto sin l铆mites |
| WordBreaker.cpp | 2015 | findNextBreakInEmailOrUrl() | O(n) | Recorre texto sin l铆mites |
| FontCollection.cpp | 2013 | getGlyphScore() | O(n²) | Llama a hb_shape sin l铆mites |
| FontCollection.cpp | 2013 | itemize() | O(n) | Recorre texto sin l铆mites |
| FontFamily.cpp | 2013 | getClosestMatch() | 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 |
| 2013 | FontCollection.cpp, FontFamily.cpp, Layout.cpp creados | El c贸digo vulnerable existe desde hace 13 a帽os |
| 2015 | OptimalLineBreaker.cpp, Hyphenator.cpp, Measurement.cpp, WordBreaker.cpp creados | El pipeline de amplificaci贸n se completa |
| 2016 | CVE-2016-2414 — DoS en Minikin | Google sab铆a que Minikin era vulnerable y lo arregl贸 |
| 2017 | GreedyLineBreaker.cpp creado | Se a帽ade una capa m谩s de amplificaci贸n |
| 2018 | LineBreaker.cpp, LayoutCore.cpp, LayoutCache.h creados | El pipeline alcanza 10 capas |
| 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 — 31 vectores documentados | Google lo cerr贸 "out of scope" |
| 2026 | Bolet铆n de julio — sin parche | Google 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:
- 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 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.
#Lostmon
#Android
#AOSP
#Ciberseguridad
#MobileSecurity
#SystemUI
#libminikin
#STA
#StructuredTextAmplification
#BugBounty
#GoogleVRP
#Xiaomi
#HackerOne
#SaludMental
#Investigaci贸nIndependiente
#BojosXtu
Manuel Garcia Pe帽a (Lostmon) · lostmon@gmail.com · lostmon.blogspot.com
Whitepaper completo: 30 de julio de 2026