1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.security.state 18 19 import android.annotation.SuppressLint 20 import android.content.Context 21 import android.content.pm.PackageManager 22 import android.os.Build 23 import android.os.Bundle 24 import android.system.Os 25 import androidx.annotation.RequiresApi 26 import androidx.security.state.SecurityPatchState.Companion.USE_VENDOR_SPL 27 import androidx.webkit.WebViewCompat 28 import java.util.regex.Pattern 29 30 /** 31 * This class is a wrapper around AOSP [android.os.SecurityStateManager] service API added in 32 * SDK 35. Support for features on older SDKs is provided on a best effort basis. 33 * 34 * Manages the retrieval and storage of security patch levels and module information for an Android 35 * device. This class provides methods to fetch the current security state of the system, including 36 * patch levels for the system, vendor, and kernel as well as module updates available through 37 * Android's update system. 38 * 39 * It utilizes Android's PackageManager and other system services to retrieve detailed 40 * security-related information, which is crucial for maintaining the security integrity of the 41 * device. 42 */ 43 public open class SecurityStateManagerCompat(private val context: Context) { 44 45 public companion object { 46 private const val TAG = "SecurityStateManager" 47 private val kernelReleasePattern: Pattern = Pattern.compile("(\\d+\\.\\d+\\.\\d+)(.*)") 48 49 private const val VENDOR_SECURITY_PATCH_PROPERTY_KEY: String = 50 "ro.vendor.build.security_patch" 51 private const val ANDROID_MODULE_METADATA_PROVIDER: String = "com.android.modulemetadata" 52 53 /** 54 * The system SPL key returned as part of the {@code Bundle} from {@code 55 * getGlobalSecurityState}. 56 */ 57 public const val KEY_SYSTEM_SPL: String = "system_spl" 58 59 /** 60 * The vendor SPL key returned as part of the {@code Bundle} from {@code 61 * getGlobalSecurityState}. 62 */ 63 public const val KEY_VENDOR_SPL: String = "vendor_spl" 64 65 /** 66 * The kernel version key returned as part of the {@code Bundle} from {@code 67 * getGlobalSecurityState}. 68 */ 69 public const val KEY_KERNEL_VERSION: String = "kernel_version" 70 } 71 72 private val packageManager: PackageManager = context.packageManager 73 74 /** 75 * Retrieves the global security state of the device, compiling various security patch levels 76 * and module information into a Bundle. This method can optionally use Google's module metadata 77 * providers to enhance the data returned. 78 * 79 * @param moduleMetadataProviderPackageName Specifies package name for system modules metadata. 80 * @return A Bundle containing keys and values representing the security state of the system, 81 * vendor, and kernel. 82 */ getGlobalSecurityStatenull83 public open fun getGlobalSecurityState( 84 moduleMetadataProviderPackageName: String = ANDROID_MODULE_METADATA_PROVIDER 85 ): Bundle { 86 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 87 return getGlobalSecurityStateFromService() 88 } 89 return Bundle().apply { 90 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 91 putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH) 92 if (USE_VENDOR_SPL) { 93 putString(KEY_VENDOR_SPL, getVendorSpl()) 94 } 95 } 96 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 97 if (moduleMetadataProviderPackageName.isNotEmpty()) { 98 putString( 99 moduleMetadataProviderPackageName, 100 getPackageVersion(moduleMetadataProviderPackageName) 101 ) 102 } 103 } 104 val kernelVersion = getKernelVersion() 105 if (kernelVersion.isNotEmpty()) { 106 putString(KEY_KERNEL_VERSION, kernelVersion) 107 } 108 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 109 addWebViewPackages(this) 110 } 111 } 112 } 113 114 /** 115 * Returns the current global security state from the system service on SDK 35+. 116 * 117 * @return A [Bundle] that contains the global security state information as string-to-string 118 * key-value pairs. 119 */ 120 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 121 @SuppressLint("WrongConstant") getGlobalSecurityStateFromServicenull122 private fun getGlobalSecurityStateFromService(): Bundle { 123 val securityStateManagerService = 124 context.getSystemService(Context.SECURITY_STATE_SERVICE) 125 as android.os.SecurityStateManager 126 val globalSecurityState = securityStateManagerService.globalSecurityState 127 if (!USE_VENDOR_SPL) { 128 globalSecurityState.remove(KEY_VENDOR_SPL) 129 } 130 return globalSecurityState 131 } 132 133 /** 134 * Fetches the security patch level (SPL) for a specific package by its package name. This is 135 * typically used to get version information for modules that may have their own update cycles 136 * independent of the system OS. 137 * 138 * @param packageName The package name for which to retrieve the SPL. 139 * @return A string representing the version, or an empty string if not available or an error 140 * occurs. 141 * @throws PackageManager.NameNotFoundException if the package name provided does not exist. 142 */ getPackageVersionnull143 internal open fun getPackageVersion(packageName: String): String { 144 if (packageName.isNotEmpty()) { 145 return try { 146 packageManager.getPackageInfo(packageName, 0).versionName ?: "" 147 } catch (e: PackageManager.NameNotFoundException) { 148 "" 149 } 150 } 151 return "" 152 } 153 154 /** 155 * Adds information about the webview packages to a bundle. This is used to track updates and 156 * versions for the webview, which can have security implications. 157 * 158 * @param bundle The bundle to which the webview package information will be added. 159 */ 160 @RequiresApi(26) addWebViewPackagesnull161 private fun addWebViewPackages(bundle: Bundle) { 162 val packageName = getCurrentWebViewPackageName() 163 if (packageName.isNotEmpty()) { 164 bundle.putString(packageName, getPackageVersion(packageName)) 165 } 166 } 167 168 /** 169 * Retrieves the current webview package name used by the system. This method checks the 170 * system's default webview provider and extracts the package name, which is essential for 171 * managing updates and security patches for components relying on webview. 172 * 173 * @return A string representing the current webview package name, or an empty string if it 174 * cannot be determined. 175 */ getCurrentWebViewPackageNamenull176 private fun getCurrentWebViewPackageName(): String { 177 val webViewPackageInfo = 178 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 179 WebViewCompat.getCurrentWebViewPackage(context) 180 } else { 181 null 182 } 183 return webViewPackageInfo?.packageName ?: "" 184 } 185 186 /** 187 * Safely retrieves the current security patch level of the device's operating system. This 188 * method ensures compatibility by checking the Android version before attempting to access APIs 189 * that are not available on older versions. 190 * 191 * @return A string representing the current security patch level, or empty string if it cannot 192 * be retrieved. 193 */ getSecurityPatchLevelSafenull194 internal fun getSecurityPatchLevelSafe(): String { 195 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 196 Build.VERSION.SECURITY_PATCH 197 } else { 198 "" 199 } 200 } 201 202 /** 203 * Attempts to retrieve the kernel version of the device using the system's uname() command. 204 * This method is used to determine the current kernel version which is a part of the security 205 * state assessment. 206 * 207 * @return A string containing the kernel version, or an empty string if an error occurs or the 208 * data cannot be reliably parsed. 209 */ getKernelVersionnull210 private fun getKernelVersion(): String { 211 try { 212 val matcher = kernelReleasePattern.matcher(Os.uname().release) 213 return if (matcher.matches()) matcher.group(1)!! else "" 214 } catch (e: Exception) { 215 return "" 216 } 217 } 218 219 /** 220 * Uses reflection to access Android's SystemProperties to retrieve the vendor security patch 221 * level. This approach is necessary on Android versions where the vendor patch level is not 222 * directly accessible via the public API. 223 * 224 * @return A string representing the vendor's security patch level, or an empty string if it 225 * cannot be retrieved. 226 */ 227 @Suppress("BanUncheckedReflection") // For accessing vendor SPL on SDK older than 35. getVendorSplnull228 private fun getVendorSpl(): String { 229 try { 230 // This is the only way to get vendor SPL from public API level on Android 14 or older 231 // devices. 232 val systemProperties = Class.forName("android.os.SystemProperties") 233 val getMethod = 234 systemProperties.getMethod("get", String::class.java, String::class.java) 235 return getMethod.invoke(systemProperties, VENDOR_SECURITY_PATCH_PROPERTY_KEY, "") 236 as String 237 } catch (e: Exception) { 238 return "" 239 } 240 } 241 } 242