bug: onConsoleMessage OOMs on low-RAM Android — String.format runs on unbounded console message before any log-level/config gate

Aperta
#8,532 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Tranquilla
Stack tecnologico
android, java, javascript

Direzione di ricerca

Inizia in android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java e segui onConsoleMessage attraverso isValidMsg, il comportamento del logging e la distribuzione per livello. Riproduci il problema con console.log('x'.repeat(100_000_000)) su un dispositivo o emulatore Android con risorse limitate; il lavoro è completato quando i messaggi sovradimensionati non causano più arresti anomali e il logging disabilitato evita il costo della formattazione.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

triage

Bug Report

Capacitor Version
@capacitor/cli:     6.2.1
@capacitor/core:    6.2.1
@capacitor/android: 6.2.1

The offending code is unchanged on main, so this affects v7 as well.

Platform(s)

Android (native WebView console bridge). Reproduces on low-RAM devices (e.g. TECNO Camon, ~192 MB WebView heap growth limit).

Current Behavior

BridgeWebChromeClient.onConsoleMessage calls String.format(...) on the entire JS console message unconditionally, before the log-level dispatch (and before any loggingBehavior gate). If a console.log/console.error argument is large — a serialized IndexedDB row, an axios error/response object, etc. — the JS string can be tens or hundreds of MB. Materializing it into a Java String blows the WebView heap on low-RAM devices → java.lang.OutOfMemoryError → hard crash.

Critically, setting loggingBehavior: 'none' / 'production' does not prevent it, because the String.format cost is paid before logging is gated.

Current code — android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java:

@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    String tag = Logger.tags("Console");
    if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) {
        String msg = String.format(              // runs on full, unbounded message
            "File: %s - Line %d - Msg: %s",
            consoleMessage.sourceId(),
            consoleMessage.lineNumber(),
            consoleMessage.message()             // can be hundreds of MB
        );
        String level = consoleMessage.messageLevel().name();
        // ... level dispatch happens AFTER the format ...
    }
    return true;
}

isValidMsg only filters specific string patterns, not size.

In our production app this was the #2 fatal crash (~5,300 events over 7 days from our Crashlytics). One affected device attempted a single ~266 MB (266,032,112-byte) allocation on every occurrence.

Note: this is a distinct code path from the OOM in #7158 (that one is in Cordova NativeToJsMessageQueue / PluginResult JSON encoding).

Expected Behavior

An oversized console message must never crash the app. The bridge should skip formatting when logging is disabled and/or bound the message length before String.format.

Code Reproduction

Minimal — in any Capacitor Android app, run in the WebView on a low-RAM device (or an emulator with a small WebView heap cap):

console.log('x'.repeat(100_000_000)); // ~100M chars

App crashes with java.lang.OutOfMemoryError. loggingBehavior: 'none' in capacitor.config does not prevent it.

Suggested Fix

Two complementary guards in onConsoleMessage:

private static final int MAX_CONSOLE_MESSAGE_LENGTH = 8 * 1024; // tunable

@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    String tag = Logger.tags("Console");
    if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) {
        // 1) don't pay the format cost if logging is disabled
        if (!Logger.shouldLog()) {          // or the equivalent loggingBehavior check
            return true;
        }
        // 2) bound the message so an oversized log can never OOM the bridge
        String message = consoleMessage.message();
        if (message.length() > MAX_CONSOLE_MESSAGE_LENGTH) {
            message = message.substring(0, MAX_CONSOLE_MESSAGE_LENGTH) + "… [truncated]";
        }
        String msg = String.format(
            "File: %s - Line %d - Msg: %s",
            consoleMessage.sourceId(), consoleMessage.lineNumber(), message);
        // ... existing level dispatch ...
    }
    return true;
}

Happy to open a PR with whatever threshold / config approach the team prefers.

Lingua principale
TypeScript
Stelle
16.7k
Fork
1.3k
Merge medio
3g 10h
PR unite (30g)
10

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ionic-team/capacitor

Tutte le issue di ionic-team/capacitor

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.