Keep settings history timestamps on 29 February

dumpsys prints the settings change history without a year, so
_resolve_timestamp() parsed the value with strptime("%m-%d ...") and only
then replaced the year. strptime() parses a value without a year in 1900,
which has no 29 February, so every change made on a leap day raised
ValueError and lost its timestamp, dropping it from the timeline and from
the alert of a dangerous setting.

Parse the value together with a year instead, starting from the year the
section was dumped and going back until the date exists and is not later
than the dump. Eight years always reach a leap year.

This also removes the DeprecationWarning Python 3.13 raises for parsing a
day of month without a year (python/cpython#70647).
This commit is contained in:
Muhammad Mohid Shafiq committed 2026-10-08 19:35:29 +05:00
1 parent 94e9cd3712
commit 10f739d669
2 files changed
+48 -8

No files matched your search

+13 -8
View File
@@ -255,19 +255,24 @@ class Settings(AndroidArtifact):
dumpsys prints the change history without a year, so it is resolved dumpsys prints the change history without a year, so it is resolved
against the time the section was dumped: the most recent matching date against the time the section was dumped: the most recent matching date
at or before that time. at or before that time.
The year is parsed together with the value, because strptime() would
otherwise parse it in 1900, which has no 29 February. Going back eight
years always reaches a leap year.
""" """
if section_end is None: if section_end is None:
return None return None
try: for year in range(section_end.year, section_end.year - 8, -1):
partial = datetime.strptime(value, "%m-%d %H:%M:%S.%f") try:
timestamp = partial.replace(year=section_end.year) timestamp = datetime.strptime(f"{year}-{value}", "%Y-%m-%d %H:%M:%S.%f")
if timestamp > section_end: except ValueError:
timestamp = partial.replace(year=section_end.year - 1) continue
except ValueError:
return None
return convert_datetime_to_iso(timestamp) if timestamp <= section_end:
return convert_datetime_to_iso(timestamp)
return None
def _parse_history( def _parse_history(
self, line: str, section_end: Optional[datetime] self, line: str, section_end: Optional[datetime]
+35
View File
@@ -3,6 +3,8 @@
# Use of this software is governed by the MVT License 1.1 that can be found at # Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
from typing import Optional
from mvt.android.artifacts.settings import Settings from mvt.android.artifacts.settings import Settings
from ..utils import get_artifact from ..utils import get_artifact
@@ -21,6 +23,21 @@ def find(settings: Settings, name: str) -> list:
return [result for result in settings.results if result["name"] == name] return [result for result in settings.results if result["name"] == name]
def resolve_history_time(time: str, section_end: str) -> Optional[str]:
settings = Settings()
settings.parse(
"SECURE SETTINGS (user 0)\n"
"_id:240 name:accessibility_enabled pkg:android value:1\n"
"\tHistory (accessibility_enabled)\n"
f"\t\ttime:{time} mode:update oldValue:0 newValue:1 "
"package:com.example.helper\n"
"\n"
"--------- 0.019s was the duration of dumpsys settings, "
f"ending at: {section_end}\n"
)
return settings.results[0]["history"][0]["timestamp"]
class TestSettingsArtifact: class TestSettingsArtifact:
def test_parsing(self): def test_parsing(self):
settings = parse_bugreport_settings() settings = parse_bugreport_settings()
@@ -159,6 +176,24 @@ class TestSettingsArtifact:
} }
] ]
def test_leap_day_history_keeps_its_timestamp(self):
assert (
resolve_history_time("02-29 22:41:07.980", "2024-03-05 23:14:28")
== "2024-02-29 22:41:07.980000"
)
def test_leap_day_history_resolved_to_the_last_leap_year(self):
# The most recent 29 February at or before the dump can be several
# years back when the dump was taken in a year without one.
assert (
resolve_history_time("02-29 22:41:07.980", "2025-03-01 10:00:00")
== "2024-02-29 22:41:07.980000"
)
assert (
resolve_history_time("02-29 22:41:07.980", "2024-01-10 10:00:00")
== "2020-02-29 22:41:07.980000"
)
def test_dangerous_setting_is_detected_with_the_changing_package(self): def test_dangerous_setting_is_detected_with_the_changing_package(self):
settings = parse_bugreport_settings() settings = parse_bugreport_settings()
settings.check_indicators() settings.check_indicators()