fix(adb): mark unknown permission coverage partial (#4663)

fix(adb): mark unknown package coverage partial

Co-authored-by: gongfpp <22683417+gongfpp@users.noreply.github.com>
This commit is contained in:
gongfppandgongfpp authored and GitHub committed 2026-10-17 17:36:19 +08:00
1 parent 6b27d8f4e3
commit 9534e42ec8
4 files changed
+66 -12

No files matched your search

+26 -2
View File
@@ -706,7 +706,7 @@ exit 1`,
expect(result.total_bytes).toBe(34);
});
it("handles `pkg=null` by returning null for requested_permissions_count", async () => {
it("marks coverage partial when `pkg=null` leaves permissions unknown", async () => {
const { binary } = await writeAdbStub(
shellStub({
"dumpsys:package": `Packages:
@@ -724,7 +724,29 @@ it("handles `pkg=null` by returning null for requested_permissions_count", async
});
expect(result).toMatchObject({
requested_permissions_count: null,
coverage: "complete",
coverage: "partial",
});
});
it("keeps permission counts unknown when package state is not established", async () => {
const { binary } = await writeAdbStub(
shellStub({
"dumpsys:package": `Packages:
Package [com.example] (abcdef):
appId=10234
codePath=/data/app/com.example
versionCode=45 minSdk=24 targetSdk=34
`,
}),
);
const result = await evidenceResult(providerOver(binary), {
operation: "inspect_adb_package",
input: { serial: "emulator-5554", package: "com.example" },
});
expect(result).toMatchObject({
version_code: "45",
requested_permissions_count: null,
coverage: "partial",
});
});
@@ -771,6 +793,7 @@ it.each(["9007199254740993", "-4294967296"])(
shellStub({
"dumpsys:package": `Packages:
Package [com.example] (abcdef):
pkg=Package{abcdef com.example}
versionCode=${versionCode} minSdk=24 targetSdk=34
`,
}),
@@ -781,6 +804,7 @@ it.each(["9007199254740993", "-4294967296"])(
});
expect(result).toMatchObject({
version_code: versionCode,
requested_permissions_count: 0,
coverage: "complete",
});
},
+4 -2
View File
@@ -1537,7 +1537,8 @@ export class AdbProvider {
pkg_flags: details.pkgFlags,
requested_permissions_count: details.requestedPermissions,
coverage:
details.versionCode === null && details.versionName === null
details.requestedPermissions === null ||
(details.versionCode === null && details.versionName === null)
? "partial"
: "complete",
},
@@ -1545,7 +1546,8 @@ export class AdbProvider {
{
limitations: [
"Fields are a fixed projection of dumpsys package output; version-dependent sections are ignored rather than guessed",
"requested_permissions_count counts the listed requested permissions section only",
"requested_permissions_count counts the listed requested permissions section; an absent section is zero only when dumpsys confirms a loaded parsed package",
"A null requested_permissions_count means dumpsys reported pkg=null or did not establish that an omitted permission section is an empty inventory",
],
},
),
+17 -1
View File
@@ -175,7 +175,23 @@ describe("parseDumpsysPackageOutput", () => {
).toBeNull();
});
it("counts listed permissions when package data is available", () => {
it("reports unknown when package state and the permission section are absent", () => {
expect(
parseDumpsysPackageOutput(
"Package [com.example] (abcdef):\n versionCode=45\n",
).requestedPermissions,
).toBeNull();
});
it("reports zero only for a confirmed loaded package without the section", () => {
expect(
parseDumpsysPackageOutput(
"Package [com.example] (abcdef):\n pkg=Package{abcdef com.example}\n",
).requestedPermissions,
).toBe(0);
});
it("counts listed permissions when the section is available", () => {
expect(
parseDumpsysPackageOutput(
"Package [com.example] (abcdef):\n requested permissions:\n android.permission.INTERNET\n",
+19 -7
View File
@@ -297,10 +297,12 @@ export const parseDumpsysPackageOutput = (
pkgFlags: null,
};
let inPermissions = false;
let hasPermissionsSection = false;
let permissions = 0;
let packageIsNull = false;
let parsedPackageAvailable: boolean | null = null;
for (const line of stdout.split(/\r?\n/u)) {
if (line.trim().startsWith("requested permissions:")) {
hasPermissionsSection = true;
inPermissions = true;
continue;
}
@@ -317,8 +319,11 @@ export const parseDumpsysPackageOutput = (
if (match === null) continue;
const key = match[1]!;
const value = match[2]!.trim();
if (key === "pkg" && value === "null") packageIsNull = true;
else if (key === "versionName" && details.versionName === null)
if (key === "pkg" && parsedPackageAvailable === null) {
if (value === "null") parsedPackageAvailable = false;
else if (value.startsWith("Package{") && value.endsWith("}"))
parsedPackageAvailable = true;
} else if (key === "versionName" && details.versionName === null)
details.versionName = value === "" ? null : value;
else if (key === "versionCode" && details.versionCode === null) {
const parsed = /^(-?\d+)/u.exec(value);
@@ -338,10 +343,17 @@ export const parseDumpsysPackageOutput = (
} else if (key === "pkgFlags" && details.pkgFlags === null)
details.pkgFlags = value === "" ? null : value;
}
return {
...details,
requestedPermissions: packageIsNull ? null : permissions,
};
// `dumpsys` omits this section both for known-empty inventories and when
// Package Manager has no parsed package; only the former is a known zero.
const requestedPermissions =
parsedPackageAvailable === false
? null
: hasPermissionsSection
? permissions
: parsedPackageAvailable === true
? 0
: null;
return { ...details, requestedPermissions };
};
/**