Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save H3XDaemon/47b44057c0d70721380fbf33f2c4d496 to your computer and use it in GitHub Desktop.

Select an option

Save H3XDaemon/47b44057c0d70721380fbf33f2c4d496 to your computer and use it in GitHub Desktop.
Technical Advisory: System Settings Persistence Failure via 64KB UTF-8 Serialization Overflow in OxygenOS / ColorOS

Technical Advisory: System Settings Persistence Failure via 64KB UTF-8 Serialization Overflow in OxygenOS / ColorOS

Component: frameworks/base/packages/SettingsProvider / com.android.providers.settings.SettingsState
Affected Platforms: OxygenOS 13.x / ColorOS 13.x (Android 13, Snapdragon 865 / SM8250 platform tested)
Impact: Complete breakdown of Settings.Secure disk persistence; all user-configured settings revert upon system reboot.


1. Executive Summary

On certain OxygenOS/ColorOS firmware releases, modifications made to system settings (including timepower_config, display timeout, accessibility, and security preferences) revert to default values after a device reboot.

Initial triage often misattributes this issue to third-party Xposed/LSPosed hooks, corrupt SQLite databases, or read-only filesystem mounts. Empirical tracing reveals the true root cause: an uncaught java.io.UTFDataFormatException within SettingsState.doWriteState(), triggered when a single OEM setting entry exceeds the 65,535-byte serialization limit enforced by Java's FastDataOutput.writeUTF() implementation.

This document details the serialization failure mechanism, traces the specific OEM string leakage key (op_sysui_qs_tiles_hide), and provides a non-destructive remediation procedure.


2. Technical Root Cause Analysis

2.1 Java 64 KB UTF-8 Serialization Boundary Violation

Android's SettingsProvider maintains in-memory setting tables (system, secure, global) that are periodically flushed to disk at /data/system/users/0/settings_*.xml.

During XML binary/text serialization, com.android.internal.util.FastDataOutput.writeUTF() serializes string attributes. Java's Modified UTF-8 specification encodes string length as an unsigned 16-bit integer (u2), capping the maximum string length at 65,535 bytes:

$$\text{Max UTF-8 Length} = 2^{16} - 1 = 65,535 \text{ bytes}$$

If a single setting string exceeds 65,535 bytes, the length calculation overflows to a negative integer value. FastDataOutput.writeUTF() throws a UTFDataFormatException:

E SettingsState: Failed to write settings, restoring backup
E SettingsState: java.io.IOException: Modified UTF-8 length too large: -66142
    at com.android.internal.util.FastDataOutput.writeUTF(FastDataOutput.java:167)
    at com.android.internal.util.BinaryXmlSerializer.attribute(BinaryXmlSerializer.java:209)
    at com.android.providers.settings.SettingsState.setValueAttribute(SettingsState.java:991)
    at com.android.providers.settings.SettingsState.writeSingleSetting(SettingsState.java:968)
    at com.android.providers.settings.SettingsState.doWriteState(SettingsState.java:853)

2.2 OEM SystemUI String Concatenation Memory Leak

Inspection of Settings.Secure revealed an OEM-specific key:

  • Key: op_sysui_qs_tiles_hide
  • Measured String Length: 66,165 characters
  • Target Package: /system_ext/priv-app/SystemUI/SystemUI.apk (classes.dex)
  • Disassembled Class: Lcom/android/systemui/qs/QSTileHost$OperatorCustom$HideTileBroadcastReceiver; (Class Def #5093, Bytecode Offsets: 0x51d9a2, 0x51dd12)

Mechanism: OxygenOS/ColorOS SystemUI dynamically updates op_sysui_qs_tiles_hide when tile visibility changes (e.g., Wi-Fi Calling status updates, SIM card state transitions, or Quick Settings customization). Within QSTileHost$OperatorCustom$HideTileBroadcastReceiver.onReceive(), string updates lack a deduplication check (.contains()). Tile package strings are repeatedly appended:

$$\text{Key Value} = \text{Key Value}_{\text{existing}} + \text{","} + \text{Tile Package Name}$$

Over extended device usage, the string size eventually passes the 65,535-byte threshold.

2.3 Transaction Abort and Disk Persistence Lockout

When any key in Settings.Secure is modified (via the Settings GUI or ADB), SettingsState schedules a batch write to disk.

When serialization reaches the oversized op_sysui_qs_tiles_hide entry, FastDataOutput.writeUTF() throws UTFDataFormatException. SettingsState catches the exception, aborts the write transaction, and restores /data/system/users/0/settings_secure.xml from the backup file on disk.

Consequently:

  1. In-memory changes persist only for the duration of the current boot session.
  2. The physical disk file /data/system/users/0/settings_secure.xml is never updated.
  3. Upon reboot, SettingsProvider reloads the stale XML file from disk, discarding all user modifications.

2.4 Secondary Hardware Alarm State Desynchronization

For scheduled power configurations (timepower_config), OxygenOS maintains a system property:

  • persist.sys.poweralarm.time (Unix epoch timestamp in milliseconds).

Because SettingsState fails to commit the updated timepower_config to disk, persist.sys.poweralarm.time retains a legacy timestamp (e.g., 1785020404000, corresponding to 07:00 AM). On system boot, init reloads persist.sys.poweralarm.time, and com.qualcomm.qti.poweroffalarm re-injects the 07:00 AM power-on alarm into SettingsProvider, enforcing a persistent state loop.


3. Diagnostic Protocol

3.1 Logcat Inspection

Filter system logs for serialization failures within SettingsState:

adb shell logcat -d | grep -i SettingsState

Look for the following signature:

E SettingsState: Failed to write settings, restoring backup : Modified UTF-8 length too large

3.2 Key Length Inspection

Identify keys exceeding byte limits using Python over ADB:

python -c "import subprocess; out=subprocess.check_output(['adb', 'shell', 'settings list secure']).decode('utf-8', errors='ignore'); [print(line[:80], '-> len=', len(line)) for line in out.splitlines() if len(line) > 1000]"

4. Remediation Procedure

Execute the following standard ADB shell commands (no root required for standard devices; use su -c if LSPosed hooks block shell writes).

Step 1: Remove the Oversized Setting Entry

adb shell settings delete secure op_sysui_qs_tiles_hide

Step 2: Clear Stale Hardware Alarm Properties

adb shell setprop persist.sys.poweralarm.time 0
adb shell setprop sys.power_off_alarm 0

Step 3: Write Target Settings Entry

adb shell settings put secure timepower_config 070000127230000127

5. Verification

5.1 Pre-Reboot Disk State Verification

Confirm that SettingsState successfully serializes to disk without throwing exceptions (requires root file read):

adb shell "su -c 'strings /data/system/users/0/settings_secure.xml | grep -A 2 -B 2 timepower_config'"

Expected Output: The target string 070000127230000127 is present in settings_secure.xml.

5.2 Post-Reboot Verification

Reboot the device:

adb reboot

After boot completion, query the setting and property states:

adb shell settings get secure timepower_config
# Expected: 070000127230000127

adb shell getprop persist.sys.poweralarm.time
# Expected: 0

6. References & Related Research

  1. AOSP Issue Tracker & FastDataOutput Specification: com.android.internal.util.FastDataOutput.writeUTF() implementation details and unsigned 16-bit string length boundaries.
  2. CVE-2024-31317 Research / PoC: SettingsState denial-of-service vector triggered via intentionally oversized string attributes in SettingsProvider. Reference Gist: https://gist.github.com/rabits/eef4fad0bd024786a3afde2bc1f32b7e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment