From 874f75bb4cf7a8bd3860bab6e8b56afa7c52f6f6 Mon Sep 17 00:00:00 2001 From: "va@resident" Date: Tue, 25 Aug 2026 22:35:33 +0300 Subject: [PATCH] Do not discard the whole tombstone on a "Caused by:" line (#892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keys are matched as bare prefixes, so `Caused by:` inside an abort message reaches the `Cause` key, fails the key comparison and raises — and the per-line loop turns that into an error that drops the entire text tombstone, stack trace included. A key mismatch means "this line is not that key", not "this file is broken": decline the line and let the remaining keys have their turn. A line with no colon is declined the same way instead of raising on the unpack. The same trap has a second form in the field, HiSilicon/Huawei tombstones printing `code around pc:` against the `code` key. --- .../android/artifacts/tombstone_crashes.py | 11 ++-- .../test_artifact_tombstone_caused_by.py | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/android/test_artifact_tombstone_caused_by.py diff --git a/src/mvt/android/artifacts/tombstone_crashes.py b/src/mvt/android/artifacts/tombstone_crashes.py index bcfb389..1b6e115 100644 --- a/src/mvt/android/artifacts/tombstone_crashes.py +++ b/src/mvt/android/artifacts/tombstone_crashes.py @@ -191,9 +191,14 @@ class TombstoneCrashArtifact(AndroidArtifact): def _load_key_value_line( self, line: str, key: str, destination_key: str, tombstone: dict ) -> bool: - line_key, value = line.split(":", 1) - if line_key != key: - raise ValueError(f"Expected key {key}, got {line_key}") + # The caller matched the key as a bare prefix, so a longer word starting + # with it arrives here: `Caused by: …` inside an abort message reaches + # the `Cause` key. That is a different line, not a broken file — say so + # by declining it, and let the remaining keys have their turn. Raising + # here discarded the whole tombstone, crash and stack trace included. + line_key, separator, value = line.partition(":") + if not separator or line_key != key: + return False value_clean = value.strip().strip("'") if destination_key == "uid": diff --git a/tests/android/test_artifact_tombstone_caused_by.py b/tests/android/test_artifact_tombstone_caused_by.py new file mode 100644 index 0000000..9a2df85 --- /dev/null +++ b/tests/android/test_artifact_tombstone_caused_by.py @@ -0,0 +1,54 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 The MVT Authors. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +"""A `Caused by:` line must not discard the whole text tombstone. + +Keys are matched as bare prefixes, so `Caused by: …` — an ordinary line inside +an abort message — reached the `Cause` key, failed the key comparison and +raised, which `Tombstones.run()` logged while dropping the entire crash record. +Seen on a 1.6 MB tombstone whose protobuf twin was zero bytes: the crash then +had no representation at all. +""" + +import datetime + +from mvt.android.artifacts.tombstone_crashes import TombstoneCrashArtifact + +TOMBSTONE = b"""\ +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +Build fingerprint: 'Xiaomi/vili_eea/vili:13/TKQ1.220829.002/V14.0.10.0:user/release-keys' +Revision: '0' +ABI: 'arm64' +Timestamp: 2023-08-24 14:54:47.999124034+0300 +Process uptime: 12199s +Cmdline: com.example.game +pid: 8044, tid: 26222, name: UnityMain >>> com.example.game <<< +uid: 10235 +signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- +Abort message: 'No pending exception expected: java.lang.SecurityException: listen + at void android.os.Parcel.readException() (Parcel.java:2920) +Caused by: android.os.RemoteException: Remote stack trace: +\tat com.android.server.TelephonyRegistry.listen(TelephonyRegistry.java:1096) +""" + +WITH_CAUSE = TOMBSTONE + b"Cause: null pointer dereference\n" + + +class TestTombstoneCausedBy: + def _parse(self, content): + artifact = TombstoneCrashArtifact() + artifact.results = [] + artifact.parse("tombstone_23", datetime.datetime(2023, 8, 24), content) + return artifact.results + + def test_caused_by_line_does_not_discard_the_tombstone(self): + results = self._parse(TOMBSTONE) + assert len(results) == 1 + assert results[0]["pid"] == 8044 + assert results[0]["process_name"] == "UnityMain" + assert results[0]["uid"] == 10235 + + def test_the_real_cause_key_is_still_parsed(self): + results = self._parse(WITH_CAUSE) + assert results[0]["cause"] == "null pointer dereference"