mirror of
https://github.com/mvt-project/mvt.git
synced 2026-09-23 09:50:46 +02:00
/proc/PID/mountinfo carries two option sets with different meaning: fields[5] are the per-mount (VFS) flags, the field after the "-" separator belongs to the superblock. A mount is writable only if both allow it. parse_mountinfo() merged both into one list and set is_read_write = "rw" in options, so a "rw" VFS mount over a read-only superblock was reported as writable. On stock Xiaomi-family builds that made every read-only mi_ext customisation overlay a HIGH "system partition is mounted as read-write". Require "rw" in both layers, and let "rw" count as a suspicious mount option only when the mount is actually writable; remount, noatime and nodiratime keep their current meaning in either layer. mount_options and options_list still carry both layers, so nothing downstream loses data. Measured on 50 bug reports carrying mountinfo - the 28 where the rule changes the output plus 22 controls, 14 brands, Android 10-16: HIGH 94 -> 0, MEDIUM 119 -> 25, the 22 controls identical, and 36233 mount entries parsed either way. Every removed alert is a read-only superblock under a "rw" VFS mount; no report gains an alert. A partition that really is writable still raises the HIGH, which the new test asserts explicitly. Fixes #936 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
44 lines
1.5 KiB
Python
44 lines
1.5 KiB
Python
# Mobile Verification Toolkit (MVT)
|
|
# Copyright (c) 2021-2026 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 mount is writable only if both mountinfo option layers allow it."""
|
|
|
|
from mvt.android.artifacts.mounts import Mounts
|
|
from mvt.common.alerts import AlertLevel
|
|
|
|
# fields[5] is the per-mount (VFS) layer, the field after "-" the superblock.
|
|
RO_OVERLAY = (
|
|
"304 166 0:130 / /product/usr rw,relatime shared:63 - overlay overlay "
|
|
"ro,seclabel,lowerdir=/mnt/vendor/ext/product/usr:/product/usr"
|
|
)
|
|
RW_SYSTEM = (
|
|
"305 166 0:131 / /system rw,relatime shared:64 - ext4 /dev/block/dm-1 "
|
|
"rw,seclabel,errors=panic"
|
|
)
|
|
|
|
|
|
def _run(line):
|
|
mounts = Mounts()
|
|
mounts.results = Mounts.parse_mountinfo(line, 1)
|
|
mounts.check_indicators()
|
|
return mounts
|
|
|
|
|
|
class TestMountsReadWriteLayers:
|
|
def test_rw_vfs_over_ro_superblock_is_not_writable(self):
|
|
mounts = _run(RO_OVERLAY)
|
|
assert mounts.results[0]["is_read_write"] is False
|
|
assert mounts.alertstore.count(AlertLevel.HIGH) == 0
|
|
assert mounts.alertstore.count(AlertLevel.MEDIUM) == 0
|
|
|
|
def test_rw_in_both_layers_still_alerts(self):
|
|
mounts = _run(RW_SYSTEM)
|
|
assert mounts.results[0]["is_read_write"] is True
|
|
assert mounts.alertstore.count(AlertLevel.HIGH) == 1
|
|
|
|
def test_both_option_layers_are_still_reported(self):
|
|
mounts = _run(RO_OVERLAY)
|
|
options = mounts.results[0]["options_list"]
|
|
assert "rw" in options and "ro" in options
|