From 9534e42ec83d1004fab94574344b2a3de64744ac Mon Sep 17 00:00:00 2001 From: gongfpp <1360187968@qq.com> Date: Sat, 17 Oct 2026 17:36:19 +0800 Subject: [PATCH] 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> --- src/adb/AdbProvider.test.ts | 28 ++++++++++++++++++++++++++-- src/adb/AdbProvider.ts | 6 ++++-- src/adb/AdbShellOutput.test.ts | 18 +++++++++++++++++- src/adb/AdbShellOutput.ts | 26 +++++++++++++++++++------- 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/adb/AdbProvider.test.ts b/src/adb/AdbProvider.test.ts index fdb2765463..c58bc2830e 100644 --- a/src/adb/AdbProvider.test.ts +++ b/src/adb/AdbProvider.test.ts @@ -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", }); }, diff --git a/src/adb/AdbProvider.ts b/src/adb/AdbProvider.ts index 34a2a2b346..dfcbce7d76 100644 --- a/src/adb/AdbProvider.ts +++ b/src/adb/AdbProvider.ts @@ -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", ], }, ), diff --git a/src/adb/AdbShellOutput.test.ts b/src/adb/AdbShellOutput.test.ts index 9774012170..6984767f15 100644 --- a/src/adb/AdbShellOutput.test.ts +++ b/src/adb/AdbShellOutput.test.ts @@ -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", diff --git a/src/adb/AdbShellOutput.ts b/src/adb/AdbShellOutput.ts index 68ecca0f71..d5241c9686 100644 --- a/src/adb/AdbShellOutput.ts +++ b/src/adb/AdbShellOutput.ts @@ -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 }; }; /**