spoof install source to Play Store for verified apps; add custom handling of stock OS packages; add cert digest and Play Store source stamp indicator to App info footer - #427
Conversation
Adjust SystemJournalNotif to support PackageIdOwnershipChecks notifications which are added by the next commit.
adevtool parses all stock OS APKs and builds an ApkParserConfig protobuf, which is included in the OS at /product/etc/apk-parser-config.pb. ApkParserConfig is checked at runtime by system_server for the following purposes: - Ownership enforcement of system-wide stock OS package IDs: package names, permission names, permission group names, content provider authorities. These IDs may be used only if the signing certificate matches. Stock OS packages and native binaries don't always perform these checks themselves, since on stock OS all of these IDs are already taken and can't be reused. Therefore, it's not safe in the general case to include a subset of stock OS packages without these checks. - Blocking installation of a subset of stock OS packages. - Isolation of specific stock OS packages from GmsCore and Play Store, or from all user-installed apps.
Add missing checks for rare edge cases.
| @Composable | ||
| fun CopyableBody(body: String) { | ||
| fun CopyableBody(bodyCharSequence: CharSequence, showDropdownTitle: Boolean = true) { | ||
| var expanded by remember { mutableStateOf(false) } |
There was a problem hiding this comment.
We should avoid renaming upstream parameters to prevent possible compile errors for future upstream code that use named parameters
e.g. This is not included in an OS build but as an example: https://github.com/muhomorr/platform_frameworks_base/blob/17_08-12_base/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt#L49
| if (permission.isTree()) { | ||
| String prefix = name + '.'; | ||
| for (String permName : ownershipMap.keySet()) { | ||
| if (!permName.startsWith(prefix)) { | ||
| continue; | ||
| } | ||
| String ownerPkgName = ownershipMap.get(permName); | ||
| String pkgName = pkg.getPackageName(); | ||
| if (!ownerPkgName.equals(pkgName)) { | ||
| pkg.recordIdOwnershipViolation("permission-tree " + name + " conflicts with " + permName + " which is owned by " + ownerPkgName); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Prefix conflicts are checked only when the incoming declaration is itself a permission tree
For example, system_ext/priv-app/GoogleServicesFramework/GoogleServicesFramework.apk provides a concrete example (although GSF isn't supposed to be used now anyway) by reserving this namespace in its manifest:
<permission-tree
android:label="Google Services Permissions"
android:name="com.google.android.googleapps.permission.GOOGLE_AUTH"/>BriefPackageInfo combines that declaration with ordinary permissions, so the generated permission_owners map retains only the exact name and loses the fact that it is a tree.
While GSF is absent, another APK can therefore declare an ordinary permission under GSF"s com.google.android.googleapps.permission.GOOGLE_AUTH namespace such as com.google.android.googleapps.permission.GOOGLE_AUTH.UNREGISTERED_CHILD
This would have no exact map entry and there is no live tree to catch it. The same gap applies to any configured stock permission tree while its owner is absent
So trying to install a user app with
<permission
android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES"
android:protectionLevel="signature" />would fail to install, since that's an existing permission in GSF
But trying to install a user app with
<permission
android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.UNREGISTERED_CHILD"
android:protectionLevel="signature" />would succeed
We should preserve tree ownership separately and reject ordinary permission declarations beneath reserved tree prefixes
There was a problem hiding this comment.
This constraint isn't enforced even for preinstalled packages. Third-party apps can declare permission-trees and permissions that are within namespace of permission-trees of preinstalled apps.
Edit: missed that such permissions are ignored during package parsing.
There was a problem hiding this comment.
Nothing uses permission-trees in AOSP and on stock OS except GSF, which isn't used since Android 15. GSF is now an android:hasCode=false package, i.e. it can never add permissions at runtime.
| for (xml::Element* manifest_child : manifest_el->GetChildElements()) { | ||
| if (manifest_child->name == "permission" || manifest_child->name == "permission-tree") { |
There was a problem hiding this comment.
permission and permission-tree are combined here
| check(digest.size == 32) | ||
| lines.add(FormattingCommand.MonospaceTextStart) | ||
| lines.add(digest.copyOfRange(0, 16).toHexString(hexFormat)) | ||
| lines.add(digest.copyOfRange(16, 32).toHexString(hexFormat)) | ||
| lines.add(FormattingCommand.MonospaceTextEnd) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return buildAnnotatedString { | ||
| var start = -1 | ||
| lines.forEachIndexed { idx, obj -> | ||
| when { | ||
| obj is String -> { | ||
| if (length != 0) { | ||
| append('\n') | ||
| } | ||
| append(obj) | ||
| } | ||
| obj == FormattingCommand.MonospaceTextStart -> { | ||
| start = length | ||
| } | ||
| obj == FormattingCommand.MonospaceTextEnd -> { | ||
| addStyle(SpanStyle(fontFamily = FontFamily.Monospace), start, length) | ||
| } |
There was a problem hiding this comment.
I think something like this (wrapping the entire getFooterText in buildAnnotatedString) would be simpler / declarative and avoids FormattingCommand, MutableList<Any>:
import androidx.compose.ui.text.withStyle
private fun getFooterText(ctx: Context): AnnotatedString = buildAnnotatedString {
fun appendLine(text: String) {
if (length > 0) append('\n')
append(text)
}
val pi = packageInfo
appendLine(pi.packageName)
pi.versionNameBidiWrapped?.let {
appendLine(ctx.getString(R.string.version_text, it))
}
// ...
val monospace = SpanStyle(fontFamily = FontFamily.Monospace)
certs.forEach { cert ->
appendLine(ctx.getString(R.string.app_info_apk_cert_digest))
val digest = cert.sha256Digest
check(digest.size == 32)
append('\n')
withStyle(monospace) {
append(digest.copyOfRange(0, 16).toHexString(hexFormat))
append('\n')
append(digest.copyOfRange(16, 32).toHexString(hexFormat))
}
}
}
Depends on:
GrapheneOS/adevtool#353
GrapheneOS/platform_packages_apps_AppCompatConfig#33
GrapheneOS/platform_packages_apps_Settings#441
Closes GrapheneOS/os-issue-tracker#5406