CVE-2026-58882 explained: how an 8-byte SMS kills Android’s phone process

The CyberSec Guru

CVE-2026-58882: The Android SMS of Death

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

On October 29, 2026, Australian security researcher Mark Blaszczyk (@sourone) published the technical specifications and proof-of-concept code for a flaw I think forces a rethink of how modern mobile operating systems handle legacy cellular protocols. Tracked as CVE-2026-58882 (and internally within the Android Open Source Project under bug ID b/517376969), the vulnerability has picked up the nickname “SMS of Death.” At its core, it’s an unauthenticated, zero-click remote denial-of-service condition in the core telephony framework of Android versions 12 through 17. By sending an 8-byte binary payload formatted as a Wireless Application Protocol (WAP) Push message to UDP/SMS destination port 2948, an attacker who knows nothing more than a target’s phone number can trigger an uncaught NegativeArraySizeException inside the persistent com.android.phone system process.

Unlike application-layer exploits that target multimedia parsers or web rendering engines for remote code execution, CVE-2026-58882 doesn’t exfiltrate credentials or install spyware. It attacks availability, at the boundary where cellular radio hardware meets the operating system’s Java framework. The malformed Protocol Data Unit (PDU) is parsed automatically by the telephony stack before any user-facing messaging client is ever invoked, so the target handset shows no incoming notification, logs no sender address in the inbox, and gives the user no chance to reject the payload.

When com.android.phone dies, active voice calls, including emergency calls to 911, 112, or Australia’s 000, immediately drop with a disconnect cause of CS_DEATH, and cellular registration disappears while the process restarts. The crash happens before the handset can send a delivery acknowledgment (RP-ACK) back to the carrier’s Short Message Service Center (SMSC), and before Android removes the message fragment from its internal SQLite staging table. That means the cellular network and the operating system end up working against the victim together: carrier retransmission timers fire the same payload back at the handset for days, and rapid bursts of roughly 20 to 30 messages can corrupt the underlying database or trigger Android’s RescueParty mechanism, locking the device into a recovery-mode boot loop that even a factory reset can’t permanently fix as long as the SIM stays registered on the network.

What makes this flaw more than a textbook parser oversight is the paper trail around its disclosure. Blaszczyk reported it to Google’s Android and Devices Vulnerability Reward Program (VRP) on May 5, 2026. Google initially triaged it as a non-security issue and closed it “Won’t Fix,” a status that reportedly held right up until the late-October public disclosure. Commit logs in the AOSP repository platform/frameworks/opt/telephony tell a different story: on June 15, 2026, Android engineer Thomas Nguyen authored commit 735d9672603fa206c4b43cb668c1aa273db70b97, titled “RESTRICT AUTOMERGE: Fix remote DoS in decodeWapPdu (b/517376969).” It was merged on August 5, 2026, distributed to OEMs in the July security preview, and rolled out across the October and November 2026 patch cycles for Android 14 through 17.

Below, I trace the exact mechanics of the bug, which means stepping through 25-year-old telecom specifications, two’s complement integer arithmetic in the Android Runtime (ART), and the state machines that govern how a smartphone talks to a cell tower.

How WAP Push bypasses the inbox entirely

To see why a user can’t just block a sender or switch messaging apps to stop CVE-2026-58882, it helps to look at how the 3rd Generation Partnership Project (3GPP) and the Open Mobile Alliance (OMA) built binary SMS routing at the turn of the millennium. Standard text messages rely on the SMS-DELIVER Protocol Data Unit defined in 3GPP TS 23.040, carrying 7-bit GSM default alphabet characters or 16-bit UCS-2 text directly to the user’s chosen messaging client. Carriers also needed a way to wake up a handset and push machine-readable instructions, such as Multimedia Messaging Service (MMS) fetch notifications, Over-The-Air (OTA) APN provisioning profiles, or Service Indication (SI) and Service Loading (SL) URIs, without dumping raw hex strings into a human’s text inbox.

The answer was WAP Push, defined under the Wireless Session Protocol (WSP) specification (WAP-230-WSP) and transported over SMS via the Wireless Datagram Protocol (WDP). When an SMS carries a WAP Push payload, the TP-UDHI (User Data Header Indicator) bit in the first octet of the SMS-DELIVER header is set to 1, and the TP-DCS (Data Coding Scheme) is configured for 8-bit binary data. Inside the User Data Header (UDH), an Information Element Identifier (IEI) of 0x05 designates Application Port Addressing using 16-bit ports. Destination port 2948 (0x0B84) is reserved worldwide by IANA and OMA for connectionless WAP Push sessions, typically paired with source port 9200 (0x23F0).

When the baseband processor (the cellular modem) receives a radio frame over the air interface and reassembles an SMS-DELIVER PDU addressed to port 2948, it passes the raw bytes up to the application processor over the Radio Interface Layer (RIL) via IRadioIndication.newSms(). Inside Android, the persistent system process com.android.phone, running under the privileged radio UID (1001), listens to these RIL indications through its GsmInboundSmsHandler (or CdmaInboundSmsHandler) state machine.

[Cellular Network SMSC]
│ (8-bit Binary SMS-DELIVER PDU, UDH IEI 0x05 -> Dest Port 2948)
▼
[Baseband Modem / RIL]
│ (IRadioIndication.newSms)
▼
[GsmInboundSmsHandler] (runs inside com.android.phone, UID 1001)
│ 1. Inserts raw PDU into SQLite: content://sms/raw
│ 2. Inspects destPort == SmsHeader.PORT_WAP_PUSH (2948)
▼
[WapPushOverSms.dispatchWapPdu()]
│ Calls decodeWapPdu(byte[] pdu, InboundSmsHandler handler)
▼
[WspTypeDecoder.decodeUintvarInteger()]
│ Parses 5-byte uintvar (88 80 80 80 00) -> long 0x80000000L
▼
[WapPushOverSms.java : Line 217 & Line 244]
│ int headerLength = (int) pduDecoder.getValue32(); // -> -2147483648
│ byte[] header = new byte[headerLength]; // -> CRASH!
▼
[Uncaught NegativeArraySizeException] ──► Process com.android.phone dies (CS_DEATH)

Before Android tries to parse or route the incoming message, InboundSmsHandler writes the message fragments into an internal SQLite table reachable at content://sms/raw via addTrackerToRawTableAndSendMessage(). This raw table is a crash-recovery journal: if the phone loses power mid-reassembly on a multi-part message, the system can rebuild the InboundSmsTracker on reboot and finish delivering the message. Once the tracker is committed to raw, InboundSmsHandler.processMessagePart() checks the destination port. Finding destPort == SmsHeader.PORT_WAP_PUSH (2948), it skips the standard SMS_DELIVER_ACTION intent broadcast, meaning apps like Google Messages are never notified, and hands the raw byte array straight to mWapPush.dispatchWapPdu(), an instance of com.android.internal.telephony.WapPushOverSms.

All of this happens inside the privileged telephony process before the user does anything. No tapped notification, no opened thread, no previewed link. If an attacker can induce an unhandled runtime exception inside WapPushOverSms, they kill the exact thread managing cellular state, active voice calls, and the raw SMS database queue.

📬 Stay Ahead of Cyber Threats

Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.

Subscribe to the Newsletter →

The five-byte integer that breaks WapPushOverSms

Inside frameworks/opt/telephony/src/java/com/android/internal/telephony/WapPushOverSms.java, the entry method dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) hands off the work of parsing the binary header to a private helper, decodeWapPdu(byte[] pdu, InboundSmsHandler handler).

A connectionless WSP Push PDU follows a fixed byte layout at the start of the payload:

  • Byte 0 (Transaction ID): An 8-bit unsigned integer used to correlate push messages with acknowledgments.
  • Byte 1 (PDU Type): An 8-bit identifier for the WSP operation. For WAP Push, this must be either WspTypeDecoder.PDU_TYPE_PUSH (0x06) or WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH (0x07).
  • Bytes 2..N (HeaderLen): A variable-length unsigned integer (uintvar) giving the combined byte length of the ContentType and Headers fields that follow.

To understand the bug, you need to know how the WAP spec defines a uintvar and how Android’s WspTypeDecoder parses it. Under section 8.1.2 of WAP-230-WSP, a uintvar encodes an unsigned integer of up to 32 bits across one to five octets. To allow variable length without a dedicated length-prefix byte, the encoding uses the most significant bit (MSB, bit 7) of each byte as a continuation flag, while the lower 7 bits (bits 0 to 6) carry the actual integer payload. A continuation bit of 1 means more bytes follow; a continuation bit of 0 marks the final byte.

In com.android.internal.telephony.WspTypeDecoder, the method decodeUintvarInteger(int startIndex) implements this state machine:

// Simplified from frameworks/opt/telephony/.../WspTypeDecoder.java
long unsignedInt; // Stored internally in mUnsigned32bit (a 64-bit Java long)
public boolean decodeUintvarInteger(int startIndex) {
int index = startIndex;
mUnsigned32bit = 0;
while ((mWspData[index] & 0x80) != 0) {
if ((index - startIndex) >= 4) {
return false; // A uintvar cannot exceed 5 octets
}
mUnsigned32bit = (mUnsigned32bit << 7) | (mWspData[index] & 0x7f);
index++;
}
mUnsigned32bit = (mUnsigned32bit << 7) | (mWspData[index] & 0x7f);
mDataLength = index - startIndex + 1;
return true;
}

Because Java has no native unsigned integer type, the original Android engineers stored the decoded 32-bit unsigned value (mUnsigned32bit) inside a 64-bit signed long, returned via pduDecoder.getValue32(). A 32-bit unsigned integer can hold values from 0 up to 2^32 – 1 (4,294,967,295, or 0xFFFFFFFFL), all of which fit fine inside the positive range of a 64-bit signed long.

The break happens inside WapPushOverSms.decodeWapPdu() right after decodeUintvarInteger() returns true:

// From frameworks/opt/telephony/.../WapPushOverSms.java (unpatched)
int index = 0;
int transactionId = pdu[index++] & 0xFF;
int pduType = pdu[index++] & 0xFF;
if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH)
&& (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
result.statusCode = Intents.RESULT_SMS_HANDLED;
return result;
}
pduDecoder = TelephonyComponentFactory.getInstance().inject(WspTypeDecoder.class.getName())
.makeWspTypeDecoder(pdu);
if (pduDecoder.decodeUintvarInteger(index) == false) {
result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
return result;
}
// Line 217: The unchecked narrowing cast
int headerLength = (int) pduDecoder.getValue32();
index += pduDecoder.getDecodedDataLength();
int headerStartIndex = index;
// ... [Content-Type parsing occurs here using pduDecoder.decodeContentType(index)] ...
// Line 244: The unvalidated array allocation
byte[] header = new byte[headerLength];
System.arraycopy(pdu, headerStartIndex, header, 0, header.length);

Look at line 217: int headerLength = (int) pduDecoder.getValue32();. The 64-bit long from getValue32() is cast down to a 32-bit signed int without any check for whether the value exceeds Integer.MAX_VALUE (2,147,483,647, or 0x7FFFFFFF) or the actual remaining length of the pdu byte array. Under section 5.1.3 of the Java Language Specification (narrowing primitive conversion), casting a long to an int just discards all but the lowest 32 bits. If bit 31 of that long is 1, which is true for any unsigned 32-bit value between 0x80000000L (2,147,483,648) and 0xFFFFFFFFL (4,294,967,295), Java’s two’s complement representation reads bit 31 as the sign bit, and headerLength instantly becomes negative.

Blaszczyk’s proof-of-concept builds a surgical 8-byte WSP payload to weaponize that truncation: 01 06 88 80 80 80 00 BE.

Walking through what happens when this 8-byte array arrives on port 2948:

  • Byte 0 (0x01): Parsed as transactionId = 1. index advances to 1.
  • Byte 1 (0x06): Parsed as pduType = 6, matching WspTypeDecoder.PDU_TYPE_PUSH. The initial guard check passes, and index advances to 2.
  • Bytes 2 to 6 (88 80 80 80 00): Passed to pduDecoder.decodeUintvarInteger(2). The bit shifts across the five octets build the 32-bit value step by step:
    • Octet 1 (0x88 = 1000 1000): MSB is 1 (continue). Lower 7 bits are 0001000 (0x08). mUnsigned32bit becomes 0x08.
    • Octet 2 (0x80 = 1000 0000): MSB is 1. Lower 7 bits are 0. mUnsigned32bit shifts left 7 (0x08 << 7 = 0x400).
    • Octet 3 (0x80): MSB is 1. mUnsigned32bit shifts left 7 (0x400 << 7 = 0x20000).
    • Octet 4 (0x80): MSB is 1. mUnsigned32bit shifts left 7 (0x20000 << 7 = 0x1000000).
    • Octet 5 (0x00): MSB is 0 (stop). One final left shift of 7 gives mUnsigned32bit = 0x1000000 × 2^7 = 0x80000000 = 2,147,483,648.
  • Line 217 cast: pduDecoder.getValue32() returns the 64-bit long 0x0000000080000000L. Cast to (int), the upper 32 bits are discarded, leaving 0x80000000 (Integer.MIN_VALUE), which in 32-bit signed two’s complement equals -2,147,483,648. index advances by 5 (the decoded data length) to 7.
  • Byte 7 (0xBE): At index = 7, decodeWapPdu() calls pduDecoder.decodeContentType(7). In WSP binary encoding, bytes with the MSB set (0x80 to 0xFF) represent “well-known media” short-integer content types, where the lower 7 bits index into the OMA table; 0xBE & 0x7F = 0x3E, which maps to application/vnd.wap.mms-message. Because 0xBE is a valid single-byte constrained media type, decodeContentType(7) succeeds immediately without an out-of-bounds error, so execution reaches line 244.
  • Line 244 allocation: The Android Runtime executes byte[] header = new byte[-2147483648];.

In the Java Virtual Machine and Android’s ART (the new-array Dalvik bytecode instruction), allocating an array with a size below zero immediately throws java.lang.NegativeArraySizeException. If the attacker had instead used a positive number larger than the 8-byte PDU, say headerLength = 500, new byte[500] would have succeeded, and the following System.arraycopy() on line 245 would have thrown an ArrayIndexOutOfBoundsException instead. That distinction is the whole bug: the callers in WapPushOverSms and InboundSmsHandler already had try-catch blocks written specifically to catch ArrayIndexOutOfBoundsException. They never caught NegativeArraySizeException, a sibling subclass under RuntimeException. By overflowing the 32-bit integer into negative range instead of just exceeding the buffer length, the 8-byte payload slips straight past the existing exception handler.

From CS_DEATH to a bricked phone

When NegativeArraySizeException escapes WapPushOverSms.decodeWapPdu(), it unwinds through WapPushOverSms.dispatchWapPdu() and up into the handler thread of InboundSmsHandler inside com.android.phone. Because no catch (RuntimeException e) block exists along this path in unpatched Android 12 through 17 builds, the uncaught exception reaches the thread’s UncaughtExceptionHandler (RuntimeInit$KillApplicationHandler). ART logs a fatal stack trace to logcat under the AndroidRuntime tag and sends SIGKILL to the com.android.phone process.

The collateral damage is immediate because that one process houses the entire Java-side telephony state machine, the TelephonyRegistry bindings, the ImsPhone / GsmCdmaPhone instances, and the Binder services connecting the OS to the cellular modem.

Immediate call termination and emergency call disruption

If the victim is on an active call, whether a standard carrier voice call over VoLTE/circuit-switched networks or an active emergency call to 000, 911, or 112, the death of com.android.phone severs the Binder link between Android’s TelecomManager service (in system_server) and the underlying Phone connection object. When Telecom detects binderDied() on the telephony connection service, it tears down all active call sessions and records a disconnect reason of DisconnectCause.ERROR_UNSPECIFIED or the internal RIL cause CS_DEATH. The cellular signal bars vanish for roughly one to two seconds while ActivityManagerService respawns the persistent com.android.phone process, reinitializes the RIL socket, and re-registers the SIM on the tower. In his disclosure, Blaszczyk specifically called out the life-safety implications under telecommunications frameworks like the Australian Communications and Media Authority’s Emergency Call Service Determination: a remote attacker who knows a target’s mobile number can repeatedly knock their handset off the network, including during an actual emergency.

The carrier retransmission feedback loop

A one- or two-second cellular drop from a single packet would just be an annoyance if the transaction ended there. The timing of the crash instead triggers an amplification loop between two independent systems: the carrier’s SMSC and Android’s local content://sms/raw table.

GSM/LTE/5G SMS delivery over the signaling plane is a transactional protocol. When the SMSC delivers an MT-ForwardSM (Mobile Terminated Forward Short Message) to the handset, it waits for an RP-ACK (Relay Protocol Acknowledgment) confirming receipt. In InboundSmsHandler, the method that dispatches the WAP Push (processMessagePart()) only calls deleteFromRawTable() and signals completion after dispatchWapPdu() returns a result code. When com.android.phone dies mid-execution inside decodeWapPdu(), two things fail: depending on modem firmware and RIL acknowledgment timing, the carrier’s SMSC may mark the delivery as failed (absent subscriber or system failure) and queue the message for automatic retransmission on its retry schedule, typically after 1 minute, 5 minutes, 30 minutes, and periodically after that up to the SMS validity period. In Blaszczyk’s carrier testing across Google Pixel 9 and Samsung Galaxy devices, a single malformed message caused the carrier network to repeatedly redeliver the payload, producing intermittent cellular blackouts lasting up to a full week.

The more damaging failure is local. InboundSmsHandler wrote the incoming PDU into the SQLite table content://sms/raw before calling WapPushOverSms.decodeWapPdu(). Because the process crashed before deleteFromRawTable() ran, the poison row stays in /data/user_de/0/com.android.providers.telephony/databases/mmssms.db with deleted = 0. Every time the device reboots, or InboundSmsHandler runs its startup sweep of incomplete messages in the raw table, it reads the poison row back out of SQLite, hands the bytes to decodeWapPdu(), and crashes again.

Multi-message barrage, SQLite corruption, and RescueParty

When an attacker escalates from a single SMS to a rapid burst of 20 to 30 malformed WAP Push messages, the failure mode shifts from transient outages to real system instability. As messages arrive from the SMSC faster than com.android.phone can finish initializing, the process enters a tight crash-restart loop. Each respawned instance collides with incoming RIL indications and concurrent transactions writing to and querying mmssms.db through the TelephonyProvider.

Repeatedly killing the process with SIGKILL mid-SQLite write introduces a race condition that can corrupt mmssms.db. Once that database is corrupted, com.android.phone crashes on every boot the moment it touches the telephony provider.

Rapid crashes of a persistent system process also trip Android’s RescueParty watchdog inside system_server. Introduced in Android 8.0 and refined since, RescueParty monitors persistent apps for crash frequency. If a core package like com.android.phone crashes more than a threshold number of times within a narrow window, RescueParty escalates through remediation levels, resetting settings caches, resetting system flags, and finally executing LEVEL_FACTORY_RESET, which forces the handset to reboot into Android Recovery roughly every 90 seconds. At the recovery screen, the user gets two options: “Try again,” which boots the OS only to crash back into recovery 90 seconds later, or “Factory data reset,” which wipes all user data.

Why a factory reset doesn’t fix it

For an ordinary user hit with a barrage of CVE-2026-58882 payloads, “Factory data reset” from Android Recovery looks like the guaranteed fix. It wipes /data, formats the user partition, and generates a clean mmssms.db with an empty raw table.

But as soon as the freshly wiped phone boots into the setup wizard and connects to the cellular network, either through a physical SIM still in the tray or an eSIM profile, the carrier’s SMSC sees the subscriber come back online and immediately flushes its queue of unacknowledged WAP Push messages to the handset. Before the user even finishes logging into their Google account, the poison PDUs arrive on port 2948, get written into the brand-new content://sms/raw table, crash com.android.phone, and brick the freshly wiped device again.

To recover a poisoned device without losing data, Blaszczyk documented a two-stage procedure requiring a secondary non-Android handset, a computer, and an active USB debugging (adb) session.

Step 1: Drain the carrier SMSC queue. Before touching the Android database, the victim has to stop the cellular network from firing new copies of the payload. For a physical nano-SIM, that means powering down the Android phone, ejecting the SIM, and inserting it into a non-Android device, most notably an iPhone. When the iPhone registers on the cellular tower, iOS receives the queued WAP Push messages from the SMSC, sends back the required RP-ACK packets at the transport layer (clearing the carrier’s retransmission queue), and silently discards the unrecognized WSP push headers without crashing. If the victim is on an eSIM, which can’t be physically moved to an iPhone without network connectivity or carrier re-provisioning, draining the queue is substantially harder. It often requires booting into Safe Mode or Airplane Mode during the brief startup window, or contacting the carrier’s engineering support to manually flush the SMSC queue for the affected number.

Step 2: Purge the local content://sms/raw table via adb. Once the SIM is out of the Android handset, preventing new over-the-air messages, the user connects the phone to a workstation running adb. Even with the SIM removed, the phone may still be crash-looping com.android.phone because of rows already stuck in content://sms/raw. During the brief window after boot when adbd comes online, the operator has to inspect the raw SMS table, temporarily grant the com.android.shell package WRITE_SMS privileges via appops (Android restricts raw SMS provider modifications by default), delete the poisoned rows, and revoke the permission:

# 1. Inspect the raw SMS staging table to identify the poison row(s) and sender address
adb shell "content query --uri content://sms/raw"
# 2. Grant the ADB shell UID temporary permission to modify the SMS ContentProvider
adb shell cmd appops set com.android.shell WRITE_SMS allow
# 3. Delete the malformed WAP Push entries from the raw table (either targeting the specific sender address or clearing stuck rows)
adb shell "content delete --uri content://sms/raw --where \"address='<ATTACKER_NUMBER>'\""
# 4. Restore default security posture by revoking WRITE_SMS from the shell
adb shell cmd appops set com.android.shell WRITE_SMS ignore

Only after the local content://sms/raw table returns “No result found” and the iPhone has drained the carrier’s SMSC retry buffer is it safe to move the SIM back into the Android handset. Expecting a non-technical user, who likely doesn’t even have Developer Options and USB debugging pre-authorized before the attack, to run appops and content provider deletions over adb isn’t realistic. For nearly all users on unpatched firmware, a sustained attack means a hard brick.

The patch Google shipped versus the one the researcher proposed

When Blaszczyk submitted his initial bug report on May 5, 2026, he included a clean, root-cause fix targeting line 217 of WapPushOverSms.java. Instead of letting pduDecoder.getValue32() undergo an unchecked narrowing cast into int headerLength, his proposed patch validated the raw 64-bit value against both zero and the actual length of the incoming pdu byte array before casting:

// Researcher's proposed root-cause fix at WapPushOverSms.java (line 217)
long rawHeaderLen = pduDecoder.getValue32();
if (rawHeaderLen < 0 || rawHeaderLen > pdu.length) {
result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
return result;
}
int headerLength = (int) rawHeaderLen;

This three-line bounds check stops the bug at the root: a 5-byte uintvar of 88 80 80 80 00 produces rawHeaderLen = 2147483648L, far greater than pdu.length (8), so decodeWapPdu() bails out immediately with RESULT_SMS_GENERIC_ERROR before any array allocation happens.

What stands out in the timeline is the gap between Google’s public bug triage and its internal engineering commits. On one track, Google’s VRP triage team told the researcher that crashing com.android.phone via SMS was a non-security availability issue and closed the ticket “Won’t Fix,” a status that reportedly stayed on the report through October 29, 2026. On a parallel track, an internal bug (b/517376969) was open, and on June 15, 2026, Android telephony engineer Thomas Nguyen authored commit 735d9672603fa206c4b43cb668c1aa273db70b97, merged by the Android Build Coastguard Worker on August 5, 2026.

Rather than adding just a bounds check on getValue32(), Google’s fix implemented a broader exception-handling and queue-purging strategy across both WapPushOverSms.java and InboundSmsHandler.java:

// 1. In WapPushOverSms.java (Commit 735d9672603fa206c4b43cb668c1aa273db70b97)
// Widened the catch block in decodeWapPdu() from ArrayIndexOutOfBoundsException to RuntimeException:
} catch (RuntimeException e) {
// Previously only caught ArrayIndexOutOfBoundsException aie
Rlog.e(TAG, "ignoring decodeWapPdu() runtime exception: " + e);
result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
}
// 2. In InboundSmsHandler.java (Commit 735d9672603fa206c4b43cb668c1aa273db70b97)
// Added a catch-all RuntimeException handler during message part processing and WAP Push dispatch
// that explicitly deletes the poison tracker from content://sms/raw:
} catch (RuntimeException e) {
loge("Exception delivering message part", e);
deleteFromRawTable(inboundSmsTracker.getDeleteWhere(),
inboundSmsTracker.getDeleteWhereArgs(),
DELETE_PERMANENTLY);
return false;
}

The engineering logic is straightforward once you see it. By widening the catch clause in decodeWapPdu() from ArrayIndexOutOfBoundsException to RuntimeException, Android now catches NegativeArraySizeException along with NullPointerException, ArithmeticException, or any other unchecked runtime fault a malformed WSP binary structure might trigger down the line. More importantly, wrapping message delivery in InboundSmsHandler.java with a catch (RuntimeException e) block that immediately calls deleteFromRawTable(..., DELETE_PERMANENTLY) neutralizes the persistence mechanism. If any incoming SMS or WAP Push PDU triggers an unexpected parser failure, Android logs the warning, permanently purges the offending row from content://sms/raw, and returns cleanly so the modem can acknowledge receipt to the carrier’s SMSC, breaking both the local boot loop and the carrier retransmission loop at once.

Why did Google’s VRP label the report “Won’t Fix” while a parallel team was authoring a patch titled “Fix remote DoS in decodeWapPdu”? Vulnerability reward programs historically draw rigid, sometimes overly bureaucratic, lines between memory corruption or privilege escalation bugs and denial-of-service bugs, and occasionally classify transient service restarts as reliability defects rather than security vulnerabilities eligible for a CVE and bounty payout. It’s also possible that b/517376969 was flagged internally through a separate channel around the same time as triage, and communication gaps between VRP analysts and the AOSP telephony team produced a generic “Won’t Fix / Non-Security Issue” closure even as the codebase was being actively patched. Either way, labeling a zero-click remote flaw that drops active emergency calls and can permanently brick unpatched handsets as “non-security” points to a real blind spot in how availability threats get weighed in mobile threat modeling.

Who’s still exposed and why endpoint defenses can’t help

The rollout of commit 735d9672603fa206c4b43cb668c1aa273db70b97 shows how fragmented Android patch distribution still is. The GrapheneOS development team confirmed merging the patch into their builds during the mid-July 2026 security preview cycle. Google shipped the fix to OEM partners in the July 2026 security preview, pushed the source to AOSP in October 2026 across branches for Android 14 through 17, and slated formal bulletin tracking across the October and November 2026 security patch levels.

That still leaves two major gaps across the global Android install base.

End-of-life devices. Because WapPushOverSms.decodeWapPdu() has contained the unchecked (int) pduDecoder.getValue32() cast since the earliest days of Android, devices running Android 12, Android 13, or older releases outside Google’s active security backport window run the exact same vulnerable bytecode and will never receive an official AOSP patch.

No user-space mitigation. On an unpatched handset, there’s no setting a user can toggle to block port 2948 while keeping cellular voice and SMS service. Revoking SMS permissions from third-party apps, switching default SMS clients, enabling Google Messages spam protection, or installing mobile endpoint detection apps provides zero protection, because WapPushOverSms runs inside com.android.phone long before any application-layer filter or BroadcastReceiver is consulted. Even Android 16 and 17’s newer radio-layer security controls, like 2G connectivity toggles and null-cipher or stingray (“SMS Blaster”) protections, don’t stop CVE-2026-58882, because the exploit doesn’t need a rogue base station or a 2G downgrade. It travels as a completely legitimate, standard binary SMS over authenticated 4G LTE and 5G networks from any carrier that permits binary or data SMS routing.

CVE-2026-58882 is a reminder that the most brittle code in a modern smartphone often sits inside legacy telecom parsers written 15 years ago and rarely touched since. The security industry spends heavily on hardening browser JIT compilers, sandboxing media codecs in Rust, and verifying kernel memory safety, but in this case a 25-year-old WAP Push specification and a single unchecked (int) cast in Java were enough to knock a flagship phone off the cellular grid in eight bytes.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

Exploits

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.