1 /*
<lambda>null2  * 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.net.Uri
22 import android.os.Build
23 import androidx.annotation.RequiresApi
24 import androidx.annotation.StringDef
25 import androidx.annotation.WorkerThread
26 import androidx.security.state.SecurityStateManagerCompat.Companion.KEY_KERNEL_VERSION
27 import androidx.security.state.SecurityStateManagerCompat.Companion.KEY_SYSTEM_SPL
28 import androidx.security.state.SecurityStateManagerCompat.Companion.KEY_VENDOR_SPL
29 import java.text.ParseException
30 import java.text.SimpleDateFormat
31 import java.util.Calendar
32 import java.util.Date
33 import java.util.Locale
34 import java.util.regex.Pattern
35 import kotlinx.serialization.SerialName
36 import kotlinx.serialization.Serializable
37 import kotlinx.serialization.SerializationException
38 import kotlinx.serialization.json.Json
39 
40 /**
41  * Provides methods to access and manage security state information for various components within a
42  * system. This class handles operations related to security patch levels, vulnerability reports,
43  * and update management.
44  *
45  * Usage examples include:
46  * - Fetching the current security patch level for specific system components.
47  * - Retrieving published security patch levels to compare against current levels.
48  * - Listing and applying security updates from designated update providers.
49  *
50  * The class uses a combination of local data storage and external data fetching to maintain and
51  * update security states.
52  *
53  * Recommended pattern of usage:
54  * - call [getVulnerabilityReportUrl] and make a request to download the JSON file containing
55  *   vulnerability report data
56  * - create SecurityPatchState object, passing in the downloaded JSON as a [String]
57  * - call [getPublishedSecurityPatchLevel] or other APIs
58  *
59  * @param context Application context used for accessing shared preferences, resources, and other
60  *   context-dependent features.
61  * @param systemModulePackageNames A list of system module package names, defaults to Google
62  *   provided system modules if none are provided. The first module on the list must be the system
63  *   modules metadata provider package.
64  * @param customSecurityStateManagerCompat An optional custom manager for obtaining security state
65  *   information. If null, a default manager is instantiated.
66  * @param vulnerabilityReportJsonString A JSON string containing vulnerability data to initialize a
67  *   [VulnerabilityReport] object.
68  *
69  *   If you only care about the Device SPL, this parameter is optional. If you need access to
70  *   Published SPL and Available SPL, you must provide this JSON string, either here in the
71  *   constructor, or later using [loadVulnerabilityReport].
72  *
73  * @constructor Creates an instance of SecurityPatchState.
74  */
75 public open class SecurityPatchState
76 @JvmOverloads
77 constructor(
78     private val context: Context,
79     private val systemModulePackageNames: List<String> = DEFAULT_SYSTEM_MODULES,
80     private val customSecurityStateManagerCompat: SecurityStateManagerCompat? = null,
81     vulnerabilityReportJsonString: String? = null
82 ) {
83     init {
84         if (vulnerabilityReportJsonString != null) {
85             loadVulnerabilityReport(vulnerabilityReportJsonString)
86         }
87     }
88 
89     private val securityStateManagerCompat =
90         customSecurityStateManagerCompat ?: SecurityStateManagerCompat(context = context)
91     private var vulnerabilityReport: VulnerabilityReport? = null
92 
93     public companion object {
94         /** Default list of Android Mainline system modules. */
95         @JvmField
96         public val DEFAULT_SYSTEM_MODULES: List<String> =
97             listOf(
98                 "com.google.android.modulemetadata",
99                 "com.google.mainline.telemetry",
100                 "com.google.mainline.adservices",
101                 "com.google.mainline.go.primary",
102                 "com.google.mainline.go.telemetry"
103             )
104 
105         /** URL for the Google-provided data of vulnerabilities from Android Security Bulletin. */
106         public const val DEFAULT_VULNERABILITY_REPORTS_URL: String =
107             "https://storage.googleapis.com/osv-android-api"
108 
109         /**
110          * System component providing ro.build.version.security_patch property value as
111          * DateBasedSpl.
112          */
113         public const val COMPONENT_SYSTEM: String = "SYSTEM"
114 
115         /** System modules component providing DateBasedSpl of system modules patch level. */
116         public const val COMPONENT_SYSTEM_MODULES: String = "SYSTEM_MODULES"
117 
118         /** Kernel component providing kernel version as VersionedSpl. */
119         public const val COMPONENT_KERNEL: String = "KERNEL"
120 
121         /**
122          * Vendor component providing ro.vendor.build.security_patch property value as DateBasedSpl.
123          */
124         internal const val COMPONENT_VENDOR: String = "VENDOR"
125 
126         /** Disabled until Android provides sufficient guidelines for the usage of Vendor SPL. */
127         internal var USE_VENDOR_SPL = false
128 
129         /**
130          * Retrieves the specific security patch level for a given component based on a security
131          * patch level string. This method determines the type of [SecurityPatchLevel] to construct
132          * based on the component type, interpreting the string as a date for date-based components
133          * or as a version number for versioned components.
134          *
135          * @param component The component indicating which type of component's patch level is being
136          *   requested.
137          * @param securityPatchLevel The string representation of the security patch level, which
138          *   could be a date or a version number.
139          * @return A [SecurityPatchLevel] instance corresponding to the specified component and
140          *   patch level string.
141          * @throws IllegalArgumentException If the input string is not in a valid format for the
142          *   specified component type, or if the component requires a specific format that the
143          *   string does not meet.
144          */
145         @JvmStatic
146         public fun getComponentSecurityPatchLevel(
147             @Component component: String,
148             securityPatchLevel: String
149         ): SecurityPatchLevel {
150             val exception = IllegalArgumentException("Unknown component: $component")
151             return when (component) {
152                 COMPONENT_SYSTEM,
153                 COMPONENT_SYSTEM_MODULES,
154                 COMPONENT_VENDOR -> {
155                     if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) {
156                         throw exception
157                     }
158                     // These components are expected to use DateBasedSpl
159                     DateBasedSecurityPatchLevel.fromString(securityPatchLevel)
160                 }
161                 COMPONENT_KERNEL -> {
162                     // These components are expected to use VersionedSpl
163                     VersionedSecurityPatchLevel.fromString(securityPatchLevel)
164                 }
165                 else -> throw exception
166             }
167         }
168 
169         /**
170          * Constructs a URL for fetching vulnerability reports based on the device's Android
171          * version.
172          *
173          * @param serverUrl The base URL of the server where vulnerability reports are stored.
174          * @return A fully constructed URL pointing to the specific vulnerability report for this
175          *   device.
176          */
177         @JvmStatic
178         @RequiresApi(26)
179         public fun getVulnerabilityReportUrl(
180             serverUrl: Uri = Uri.parse(DEFAULT_VULNERABILITY_REPORTS_URL)
181         ): Uri {
182             val newEndpoint = "v1/android_sdk_${Build.VERSION.SDK_INT}.json"
183             return serverUrl.buildUpon().appendEncodedPath(newEndpoint).build()
184         }
185     }
186 
187     /** Annotation for defining the component to use. */
188     @Retention(AnnotationRetention.SOURCE)
189     @StringDef(
190         open = true,
191         value =
192             [
193                 COMPONENT_SYSTEM,
194                 COMPONENT_SYSTEM_MODULES,
195                 COMPONENT_KERNEL,
196                 COMPONENT_VENDOR,
197             ]
198     )
199     internal annotation class Component
200 
201     /** Severity of reported security issues. */
202     public enum class Severity {
203         /** Critical severity issues from Android Security Bulletin. */
204         CRITICAL,
205         /** High severity issues from Android Security Bulletin. */
206         HIGH,
207         /** Moderate severity issues from Android Security Bulletin. */
208         MODERATE,
209         /** Low severity issues from Android Security Bulletin. */
210         LOW
211     }
212 
213     /** Abstract base class representing a security patch level. */
214     public abstract class SecurityPatchLevel : Comparable<SecurityPatchLevel> {
215         abstract override fun toString(): String
216     }
217 
218     /** Implementation of [SecurityPatchLevel] for a simple string patch level. */
219     public class GenericStringSecurityPatchLevel(private val patchLevel: String) :
220         SecurityPatchLevel() {
221 
222         override fun toString(): String = patchLevel
223 
224         override fun compareTo(other: SecurityPatchLevel): Int {
225             return when (other) {
226                 is GenericStringSecurityPatchLevel -> patchLevel.compareTo(other.patchLevel)
227                 else ->
228                     throw IllegalArgumentException(
229                         "Cannot compare GenericStringSpl with different type."
230                     )
231             }
232         }
233     }
234 
235     /** Implementation of [SecurityPatchLevel] for a date-based patch level. */
236     public class DateBasedSecurityPatchLevel(
237         private val year: Int,
238         private val month: Int,
239         private val day: Int
240     ) : SecurityPatchLevel() {
241 
242         public companion object {
243             private val DATE_FORMATS = listOf("yyyy-MM", "yyyy-MM-dd")
244 
245             /**
246              * Creates a new [DateBasedSecurityPatchLevel] from a string representation of the date.
247              *
248              * @param value The date string in the format of [DATE_FORMATS].
249              * @return A new [DateBasedSecurityPatchLevel] representing the date.
250              * @throws IllegalArgumentException if the date string is not in the correct format.
251              */
252             @JvmStatic
253             public fun fromString(value: String): DateBasedSecurityPatchLevel {
254                 var date: Date? = null
255                 for (dateFormat in DATE_FORMATS) {
256                     try {
257                         date =
258                             SimpleDateFormat(dateFormat, Locale.US)
259                                 .apply {
260                                     isLenient = false // Set the date parsing to be strict
261                                 }
262                                 .parse(value)
263                     } catch (e: ParseException) {
264                         // Ignore and try other date format.
265                     }
266                 }
267                 if (date != null) {
268                     val calendar = Calendar.getInstance()
269                     calendar.time = date
270                     val year = calendar.get(Calendar.YEAR)
271                     /* Calendar.MONTH is zero-based */
272                     val month = calendar.get(Calendar.MONTH) + 1
273                     val day = calendar.get(Calendar.DAY_OF_MONTH)
274                     return DateBasedSecurityPatchLevel(year, month, day)
275                 } else {
276                     throw IllegalArgumentException(
277                         "Invalid date format. Expected formats: $DATE_FORMATS",
278                     )
279                 }
280             }
281         }
282 
283         @SuppressLint("DefaultLocale")
284         override fun toString(): String = String.format("%d-%02d-%02d", year, month, day)
285 
286         override fun compareTo(other: SecurityPatchLevel): Int {
287             if (other is DateBasedSecurityPatchLevel) {
288                 return when {
289                     year != other.year -> year - other.year
290                     month != other.month -> month - other.month
291                     else -> day - other.day
292                 }
293             } else {
294                 throw IllegalArgumentException("Cannot compare DateBasedSpl with different type.")
295             }
296         }
297 
298         /** Year of the security patch level. */
299         public fun getYear(): Int = year
300 
301         /** Month of the security patch level. */
302         public fun getMonth(): Int = month
303 
304         /** Day of the security patch level. */
305         public fun getDay(): Int = day
306     }
307 
308     /** Implementation of [SecurityPatchLevel] for a versioned patch level. */
309     public class VersionedSecurityPatchLevel(
310         private val majorVersion: Int,
311         private val minorVersion: Int,
312         private val buildVersion: Int = 0,
313         private val patchVersion: Int = 0
314     ) : SecurityPatchLevel() {
315 
316         public companion object {
317             /**
318              * Creates a new [VersionedSecurityPatchLevel] from a string representation of the
319              * version.
320              *
321              * @param value The version string in the format of "major.minor.build.patch".
322              * @return A new [VersionedSecurityPatchLevel] representing the version.
323              * @throws IllegalArgumentException if the version string is not in the correct format.
324              */
325             @JvmStatic
326             public fun fromString(value: String): VersionedSecurityPatchLevel {
327                 val parts = value.split(".")
328                 if (parts.size < 2) {
329                     throw IllegalArgumentException(
330                         "Invalid version format. Expected at least major and minor versions."
331                     )
332                 }
333 
334                 val major =
335                     parts[0].toIntOrNull()
336                         ?: throw IllegalArgumentException("Major version is not a valid number.")
337                 val minor =
338                     parts[1].toIntOrNull()
339                         ?: throw IllegalArgumentException("Minor version is not a valid number.")
340                 val patch: Int
341                 val build: Int
342                 if (parts.size > 3) {
343                     build = parts[2].toIntOrNull() ?: 0
344                     patch = parts[3].toIntOrNull() ?: 0
345                 } else if (parts.size == 3) {
346                     build = 0
347                     patch = parts[2].toIntOrNull() ?: 0
348                 } else {
349                     build = 0
350                     patch = 0
351                 }
352 
353                 return VersionedSecurityPatchLevel(major, minor, build, patch)
354             }
355         }
356 
357         @SuppressLint("DefaultLocale")
358         override fun toString(): String {
359             // Include the build version if it is non-zero
360             return when {
361                 buildVersion > 0 ->
362                     String.format(
363                         "%d.%d.%d.%d",
364                         majorVersion,
365                         minorVersion,
366                         buildVersion,
367                         patchVersion
368                     )
369                 patchVersion > 0 ->
370                     String.format("%d.%d.%d", majorVersion, minorVersion, patchVersion)
371                 else -> String.format("%d.%d", majorVersion, minorVersion)
372             }
373         }
374 
375         override fun compareTo(other: SecurityPatchLevel): Int {
376             if (other is VersionedSecurityPatchLevel) {
377                 return when {
378                     majorVersion != other.majorVersion -> majorVersion - other.majorVersion
379                     minorVersion != other.minorVersion -> minorVersion - other.minorVersion
380                     patchVersion != other.patchVersion -> patchVersion - other.patchVersion
381                     else -> buildVersion - other.buildVersion
382                 }
383             } else {
384                 throw IllegalArgumentException(
385                     "Cannot compare VersionedSecurityPatchLevel with different type"
386                 )
387             }
388         }
389 
390         /** Major version of the security patch level. */
391         public fun getMajorVersion(): Int = majorVersion
392 
393         /** Minor version of the security patch level. */
394         public fun getMinorVersion(): Int = minorVersion
395 
396         /** Patch version of the security patch level. */
397         public fun getPatchVersion(): Int = patchVersion
398 
399         /** Build version of the security patch level. */
400         public fun getBuildVersion(): Int = buildVersion
401     }
402 
403     @Serializable
404     private data class VulnerabilityReport(
405         /* Key is the SPL date yyyy-MM-dd */
406         val vulnerabilities: Map<String, List<VulnerabilityGroup>>,
407 
408         /* Key is the SPL date yyyy-MM-dd, values are kernel versions */
409         @SerialName("kernel_lts_versions") val kernelLtsVersions: Map<String, List<String>>
410     )
411 
412     @Serializable
413     private data class VulnerabilityGroup(
414         @SerialName("cve_identifiers") val cveIdentifiers: List<String>,
415         @SerialName("asb_identifiers") val asbIdentifiers: List<String>,
416         val severity: String,
417         val components: List<String>
418     )
419 
420     /**
421      * Retrieves a list of all system modules, defaulting to a predefined list of Google system
422      * modules if no custom modules are provided.
423      *
424      * @return A list of strings representing system module identifiers.
425      */
426     internal fun getSystemModules(): List<String> {
427         return systemModulePackageNames.ifEmpty { DEFAULT_SYSTEM_MODULES }
428     }
429 
430     /**
431      * Parses a JSON string to extract vulnerability report data. This method validates the format
432      * of the input JSON and constructs a [VulnerabilityReport] object, preparing the class to
433      * provide published and available security state information.
434      *
435      * @param jsonString The JSON string containing the vulnerability data.
436      * @throws IllegalArgumentException if the JSON input is malformed or contains invalid data.
437      */
438     @WorkerThread
439     public fun loadVulnerabilityReport(jsonString: String) {
440         val result: VulnerabilityReport
441 
442         try {
443             val json = Json { ignoreUnknownKeys = true }
444             result = json.decodeFromString<VulnerabilityReport>(jsonString)
445         } catch (e: SerializationException) {
446             throw IllegalArgumentException("Malformed JSON input: ${e.message}")
447         }
448 
449         val dateFormat = SimpleDateFormat("yyyy-MM-dd")
450         dateFormat.isLenient = false
451 
452         result.vulnerabilities.keys.forEach { date ->
453             try {
454                 dateFormat.parse(date)
455             } catch (e: Exception) {
456                 throw IllegalArgumentException(
457                     "Invalid format in date key for vulnerabilities (yyyy-MM-dd): $date"
458                 )
459             }
460         }
461 
462         result.kernelLtsVersions.forEach { kv ->
463             try {
464                 dateFormat.parse(kv.key)
465             } catch (e: Exception) {
466                 throw IllegalArgumentException(
467                     "Invalid format in date key for kernel LTS versions (yyyy-MM-dd): ${kv.key}"
468                 )
469             }
470 
471             kv.value.forEach {
472                 val majorVersion: Int
473                 try {
474                     majorVersion = VersionedSecurityPatchLevel.fromString(it).getMajorVersion()
475                 } catch (e: Exception) {
476                     throw IllegalArgumentException("Invalid format in kernel LTS version: $it")
477                 }
478 
479                 if (majorVersion < 4 || majorVersion > 20) {
480                     throw IllegalArgumentException("Invalid format in kernel LTS version: $it")
481                 }
482             }
483         }
484 
485         val cvePattern = Pattern.compile("CVE-\\d{4}-\\d{4,}")
486         val asbPattern = Pattern.compile("(ASB|PUB)-A-\\d{4,}")
487 
488         result.vulnerabilities.values.flatten().forEach { group ->
489             group.cveIdentifiers.forEach { cve ->
490                 if (!cvePattern.matcher(cve).matches()) {
491                     throw IllegalArgumentException(
492                         "CVE identifier does not match the required format (CVE-XXXX-XXXX): $cve"
493                     )
494                 }
495             }
496 
497             group.asbIdentifiers.forEach { asb ->
498                 if (!asbPattern.matcher(asb).matches()) {
499                     throw IllegalArgumentException(
500                         "ASB identifier $asb does not match the required format: $asbPattern"
501                     )
502                 }
503             }
504 
505             try {
506                 Severity.valueOf(group.severity.uppercase(Locale.US))
507             } catch (e: IllegalArgumentException) {
508                 throw IllegalArgumentException(
509                     "Severity must be: critical, high, moderate, low. Found: ${group.severity}"
510                 )
511             }
512         }
513 
514         vulnerabilityReport = result
515     }
516 
517     private fun getMaxComponentSecurityPatchLevel(
518         @Component component: String
519     ): DateBasedSecurityPatchLevel? {
520         if (vulnerabilityReport == null) return null
521 
522         // Iterate through all SPL dates, find the latest date where
523         // the specified component is included
524         return vulnerabilityReport!!
525             .vulnerabilities
526             .filter { entry -> entry.value.any { group -> component in group.components } }
527             .keys
528             .maxByOrNull { it }
529             ?.let { latestDate -> DateBasedSecurityPatchLevel.fromString(latestDate) }
530     }
531 
532     private fun componentToString(@Component component: String): String {
533         return component.lowercase(Locale.US)
534     }
535 
536     private fun checkVulnerabilityReport() {
537         if (vulnerabilityReport == null)
538             throw IllegalStateException("No vulnerability report data available.")
539     }
540 
541     /**
542      * Returns min SPL of the unpatched system modules, or max SPL of the system modules if all of
543      * them are fully patched.
544      */
545     private fun getSystemModulesSecurityPatchLevel(): DateBasedSecurityPatchLevel {
546         checkVulnerabilityReport()
547 
548         val modules: List<String> = getSystemModules()
549         var minSpl = DateBasedSecurityPatchLevel(1970, 1, 1)
550         var maxSpl = DateBasedSecurityPatchLevel(1970, 1, 1)
551         var unpatched = false
552         modules.forEach { module ->
553             val maxComponentSpl = getMaxComponentSecurityPatchLevel(module) ?: return@forEach
554             val packageSpl: DateBasedSecurityPatchLevel
555             try {
556                 packageSpl =
557                     DateBasedSecurityPatchLevel.fromString(
558                         securityStateManagerCompat.getPackageVersion(module)
559                     )
560             } catch (e: Exception) {
561                 // Prevent malformed package versions from interrupting the loop.
562                 return@forEach
563             }
564 
565             if (packageSpl < maxComponentSpl) {
566                 if (unpatched) {
567                     if (minSpl > packageSpl) minSpl = packageSpl
568                 } else {
569                     minSpl = packageSpl
570                     unpatched = true
571                 }
572             }
573             if (maxComponentSpl > maxSpl) {
574                 maxSpl = maxComponentSpl
575             }
576         }
577 
578         if (unpatched) {
579             return minSpl
580         }
581         if (maxSpl.getYear() == 1970) {
582             throw IllegalStateException("No SPL data available for system modules.")
583         }
584         return maxSpl
585     }
586 
587     private fun getSystemModulesPublishedSecurityPatchLevel(): DateBasedSecurityPatchLevel {
588         checkVulnerabilityReport()
589 
590         val modules: List<String> = getSystemModules()
591         var maxSpl = DateBasedSecurityPatchLevel(1970, 1, 1)
592         modules.forEach { module ->
593             val maxComponentSpl = getMaxComponentSecurityPatchLevel(module) ?: return@forEach
594 
595             if (maxComponentSpl > maxSpl) {
596                 maxSpl = maxComponentSpl
597             }
598         }
599         return maxSpl
600     }
601 
602     /**
603      * Retrieves the current security patch level for a specified component.
604      *
605      * @param component The component for which the security patch level is requested.
606      * @return A [SecurityPatchLevel] representing the current patch level of the component.
607      * @throws IllegalStateException if the patch level data is not available.
608      * @throws IllegalArgumentException if the component name is unrecognized.
609      */
610     public open fun getDeviceSecurityPatchLevel(@Component component: String): SecurityPatchLevel {
611         val globalSecurityState =
612             securityStateManagerCompat.getGlobalSecurityState(getSystemModules()[0])
613 
614         return when (component) {
615             COMPONENT_SYSTEM_MODULES -> {
616                 getSystemModulesSecurityPatchLevel()
617             }
618             COMPONENT_KERNEL -> {
619                 val kernelVersion =
620                     globalSecurityState.getString(KEY_KERNEL_VERSION)
621                         ?: throw IllegalStateException("Kernel version not available.")
622 
623                 VersionedSecurityPatchLevel.fromString(kernelVersion)
624             }
625             COMPONENT_SYSTEM -> {
626                 val systemSpl =
627                     globalSecurityState.getString(KEY_SYSTEM_SPL)
628                         ?: throw IllegalStateException("System SPL not available.")
629 
630                 DateBasedSecurityPatchLevel.fromString(systemSpl)
631             }
632             COMPONENT_VENDOR -> {
633                 val vendorSpl =
634                     globalSecurityState.getString(KEY_VENDOR_SPL)
635                         ?: throw IllegalStateException("Vendor SPL not available.")
636 
637                 DateBasedSecurityPatchLevel.fromString(vendorSpl)
638             }
639             else -> throw IllegalArgumentException("Unknown component: $component")
640         }
641     }
642 
643     /**
644      * Retrieves the published security patch level for a specified component. This patch level is
645      * based on the most recent vulnerability reports, which is a machine-readable data from Android
646      * and other security bulletins.
647      *
648      * The published security patch level is the most recent value published in a bulletin.
649      *
650      * @param component The component for which the published patch level is requested.
651      * @return A list of [SecurityPatchLevel] representing the published patch levels. The list
652      *   contains single element for all components, except for KERNEL, where it lists kernel LTS
653      *   version numbers for all supported major kernel versions. For example: ``` [ "4.19.314",
654      *   "5.15.159", "6.1.91" ] ```
655      * @throws IllegalStateException if the vulnerability report is not loaded or if patch level
656      *   data is unavailable.
657      * @throws IllegalArgumentException if the component name is unrecognized.
658      */
659     public open fun getPublishedSecurityPatchLevel(
660         @Component component: String
661     ): List<SecurityPatchLevel> {
662         checkVulnerabilityReport()
663 
664         return when (component) {
665             COMPONENT_SYSTEM_MODULES -> listOf(getSystemModulesPublishedSecurityPatchLevel())
666             COMPONENT_SYSTEM,
667             COMPONENT_VENDOR -> {
668                 val exception = IllegalStateException("SPL data not available: $component")
669                 if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) {
670                     throw exception
671                 }
672                 listOf(
673                     getMaxComponentSecurityPatchLevel(componentToString(component))
674                         ?: throw exception
675                 )
676             }
677             COMPONENT_KERNEL -> getPublishedKernelVersions()
678             else -> throw IllegalArgumentException("Unknown component: $component")
679         }
680     }
681 
682     /**
683      * Retrieves a list of the latest kernel LTS versions from the vulnerability report.
684      *
685      * @return A list of [VersionedSecurityPatchLevel] representing kernel LTS versions, or an empty
686      *   list if no data is available.
687      */
688     private fun getPublishedKernelVersions(): List<VersionedSecurityPatchLevel> {
689         vulnerabilityReport?.let { (_, kernelLtsVersions) ->
690             if (kernelLtsVersions.isEmpty()) {
691                 return emptyList()
692             }
693             // A map from a kernel LTS version (major.minor) to its latest published version.
694             // For example, version 5.4 would map to 5.4.123 if that's the latest published version.
695             val kernelVersionToLatest = mutableMapOf<String, VersionedSecurityPatchLevel>()
696             // Reduce all the published kernel LTS versions from each SPL into one list.
697             val publishedKernelLtsVersions =
698                 kernelLtsVersions.values
699                     .reduce { versions, version -> versions + version }
700                     .map { VersionedSecurityPatchLevel.fromString(it) }
701 
702             // Update the map so that each kernel LTS version maps to its latest (largest) published
703             // version.
704             publishedKernelLtsVersions.forEach { version ->
705                 val kernelVersion = "${version.getMajorVersion()}.${version.getMinorVersion()}"
706 
707                 kernelVersionToLatest[kernelVersion]?.let {
708                     if (version > it) {
709                         kernelVersionToLatest[kernelVersion] = version
710                     }
711                 } ?: run { kernelVersionToLatest[kernelVersion] = version }
712             }
713             return kernelVersionToLatest.values.toList()
714         }
715         return emptyList()
716     }
717 
718     /**
719      * Lists all security fixes applied on the current device since the baseline Android release of
720      * the current system image, filtered for a specified component and patch level, categorized by
721      * severity.
722      *
723      * @param component The component for which security fixes are listed.
724      * @param spl The security patch level for which fixes are retrieved.
725      * @return A map categorizing CVE identifiers by their severity for the specified patch level.
726      *   For example: ``` { Severity.CRITICAL: ["CVE-2023-1234", "CVE-2023-5678"], Severity.HIGH:
727      *   ["CVE-2023-9012"], Severity.MODERATE: ["CVE-2023-3456"] } ```
728      * @throws IllegalArgumentException if the specified component is not valid for fetching
729      *   security fixes.
730      * @throws IllegalStateException if the vulnerability report is not loaded.
731      */
732     public open fun getPatchedCves(
733         @Component component: String,
734         spl: SecurityPatchLevel
735     ): Map<Severity, Set<String>> {
736         // Check if the component is valid for this operation
737         val validComponents =
738             listOfNotNull(
739                 COMPONENT_SYSTEM,
740                 if (USE_VENDOR_SPL) COMPONENT_VENDOR else null,
741                 COMPONENT_SYSTEM_MODULES
742             )
743         if (component !in validComponents) {
744             throw IllegalArgumentException(
745                 "Component must be one of $validComponents but was $component"
746             )
747         }
748         checkVulnerabilityReport()
749 
750         vulnerabilityReport!!.let { report ->
751             val relevantFixes = mutableMapOf<Severity, MutableList<String>>()
752 
753             // Iterate through all vulnerabilities and filter based on component and patch level
754             report.vulnerabilities.forEach { (patchLevel, groups) ->
755                 if (spl.toString() >= patchLevel) {
756                     groups
757                         .filter { group ->
758                             when (component) {
759                                 COMPONENT_SYSTEM_MODULES ->
760                                     group.components.any { it in getSystemModules() }
761                                 else -> group.components.contains(componentToString(component))
762                             }
763                         }
764                         .forEach { group ->
765                             val severity = Severity.valueOf(group.severity.uppercase(Locale.US))
766                             relevantFixes
767                                 .getOrPut(severity, ::mutableListOf)
768                                 .addAll(group.cveIdentifiers)
769                         }
770                 }
771             }
772             return relevantFixes.mapValues { it.value.toSet() }.toMap()
773         }
774     }
775 
776     /**
777      * Checks if all components of the device have their security patch levels up to date with the
778      * published security patch levels. This method compares the device's current security patch
779      * level against the latest published levels for each component.
780      *
781      * @return true if all components are fully updated, false otherwise.
782      * @throws IllegalArgumentException if device or published security patch level for a component
783      *   cannot be accessed.
784      */
785     public fun isDeviceFullyUpdated(): Boolean {
786         checkVulnerabilityReport()
787 
788         val components =
789             listOf(
790                 COMPONENT_SYSTEM,
791                 COMPONENT_SYSTEM_MODULES,
792                 COMPONENT_VENDOR,
793                 COMPONENT_KERNEL,
794             )
795 
796         components.forEach { component ->
797             if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) return@forEach
798             val deviceSpl =
799                 try {
800                     getDeviceSecurityPatchLevel(component)
801                 } catch (e: Exception) {
802                     throw IllegalStateException(
803                         "Failed to retrieve device SPL for component: $component",
804                         e
805                     )
806                 }
807 
808             try {
809                 if (component != COMPONENT_KERNEL) {
810                     val publishedSpl = getPublishedSecurityPatchLevel(component)[0]
811 
812                     if (deviceSpl < publishedSpl) {
813                         return false
814                     }
815                 } else {
816                     val publishedVersions = getPublishedKernelVersions()
817                     val kernelVersion = deviceSpl as VersionedSecurityPatchLevel
818 
819                     if (
820                         publishedVersions
821                             .filter { it.getMajorVersion() == kernelVersion.getMajorVersion() }
822                             .any { it > kernelVersion }
823                     ) {
824                         return false
825                     }
826                 }
827             } catch (e: Exception) {
828                 throw IllegalStateException(
829                     "Published SPL not available for component: $component",
830                     e
831                 )
832             }
833         }
834         return true
835     }
836 
837     /**
838      * Verifies if all specified CVEs have been patched in the system. This method aggregates the
839      * CVEs patched across specified system components and checks if the list includes all CVEs
840      * provided.
841      *
842      * @param cveList A list of CVE identifiers as strings in the form "CVE-YYYY-NNNNN", where YYYY
843      *   denotes year, and NNNNN is a number with 3 to 5 digits.
844      * @return true if all provided CVEs are patched, false otherwise.
845      */
846     public fun areCvesPatched(cveList: List<String>): Boolean {
847         val componentsToCheck =
848             listOfNotNull(
849                 COMPONENT_SYSTEM,
850                 if (USE_VENDOR_SPL) COMPONENT_VENDOR else null,
851                 COMPONENT_SYSTEM_MODULES
852             )
853         val allPatchedCves = mutableSetOf<String>()
854 
855         // Aggregate all CVEs from security fixes across necessary components
856         for (component in componentsToCheck) {
857             val spl = getDeviceSecurityPatchLevel(component)
858             val fixes = getPatchedCves(component, spl)
859             allPatchedCves.addAll(fixes.values.flatten())
860         }
861 
862         // Check if all provided CVEs are in the patched CVEs list
863         return cveList.all { allPatchedCves.contains(it) }
864     }
865 }
866