Skip to content

Commit b5458cc

Browse files
authored
security: safely parse YAML subscriptions (#3)
* security: safely parse YAML subscriptions * security: load Clash YAML via SafeConstructor load() for type compatibility * docs: clarify codePointLimit propagation via SafeConstructor
1 parent 4270791 commit b5458cc

2 files changed

Lines changed: 20 additions & 5 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ dependencies {
6666
implementation("com.blacksquircle.ui:language-json:2.6.0")
6767

6868
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.3")
69-
implementation("org.yaml:snakeyaml:1.30")
69+
implementation("org.yaml:snakeyaml:2.3")
7070
implementation("com.github.daniel-stoneuk:material-about-library:3.2.0-rc01")
7171
implementation("com.jakewharton:process-phoenix:2.1.2")
7272
implementation("com.esotericsoftware:kryo:5.2.1")

app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ import org.ini4j.Ini
3232
import org.json.JSONArray
3333
import org.json.JSONObject
3434
import org.json.JSONTokener
35-
import org.yaml.snakeyaml.TypeDescription
35+
import org.yaml.snakeyaml.LoaderOptions
3636
import org.yaml.snakeyaml.Yaml
37+
import org.yaml.snakeyaml.constructor.SafeConstructor
3738
import org.yaml.snakeyaml.error.YAMLException
3839
import java.io.StringReader
3940
import androidx.core.net.toUri
@@ -254,9 +255,23 @@ object RawUpdater : GroupUpdater() {
254255

255256
try {
256257

257-
val yaml = Yaml().apply {
258-
addTypeDescription(TypeDescription(String::class.java, "str"))
259-
}.loadAs(text, Map::class.java)
258+
// Parse untrusted subscription YAML safely.
259+
// SafeConstructor only produces standard types (Map/List/String/
260+
// Number/Boolean/null) and rejects custom/global tags such as
261+
// !!javax..., preventing arbitrary class instantiation
262+
// (CVE-2022-1471 style gadget chains). A code-point limit bounds
263+
// input size to mitigate decompression/billion-laughs style DoS.
264+
// Note: load() is used instead of loadAs(..., Map::class.java)
265+
// because SafeConstructor cannot construct an explicit root type
266+
// tag; a YAML mapping is returned as a LinkedHashMap natively.
267+
val loaderOptions = LoaderOptions().apply {
268+
codePointLimit = 10 * 1024 * 1024 // 10 MiB
269+
}
270+
// In SnakeYAML 2.x, Yaml(BaseConstructor) adopts the constructor's
271+
// LoaderOptions (getLoadingConfig()), so codePointLimit set above is
272+
// enforced during parsing (verified: input over the limit throws).
273+
val yaml = Yaml(SafeConstructor(loaderOptions)).load<Any?>(text) as? Map<String, Any?>
274+
?: error(app.getString(R.string.no_proxies_found_in_file))
260275

261276
val globalClientFingerprint = yaml["global-client-fingerprint"]?.toString() ?: ""
262277

0 commit comments

Comments
 (0)