Skip to content

Commit

Permalink
Default to not parsing anchors and aliases to prevent "billion laughs…
Browse files Browse the repository at this point in the history
…"-style attacks.
  • Loading branch information
charleskorn committed Mar 18, 2023
1 parent fc3a270 commit 5f82a2d
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 33,7 @@ package com.charleskorn.kaml
* * [sequenceStyle]: how sequences (aka lists and arrays) should be formatted. See [SequenceStyle] for an example of each
* * [ambiguousQuoteStyle]: how strings should be escaped when [singleLineStringStyle] is [SingleLineStringStyle.PlainExceptAmbiguous] and the value is ambiguous
* * [sequenceBlockIndent]: number of spaces to use as indentation for sequences, if [sequenceStyle] set to [SequenceStyle.Block]
* * [allowAnchorsAndAliases]: set to true to allow anchors and aliases when decoding YAML (defaults to `false`)
*/
public data class YamlConfiguration constructor(
internal val encodeDefaults: Boolean = true,
Expand All @@ -47,6 48,7 @@ public data class YamlConfiguration constructor(
internal val multiLineStringStyle: MultiLineStringStyle = singleLineStringStyle.multiLineStringStyle,
internal val ambiguousQuoteStyle: AmbiguousQuoteStyle = AmbiguousQuoteStyle.DoubleQuoted,
internal val sequenceBlockIndent: Int = 0,
internal val allowAnchorsAndAliases: Boolean = false,
)

public enum class PolymorphismStyle {
Expand Down
2 changes: 2 additions & 0 deletions src/commonMain/kotlin/com/charleskorn/kaml/YamlException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 98,5 @@ public class NoAnchorForExtensionException(
path: YamlPath,
) :
YamlException("The key '$key' starts with the extension definition prefix '$extensionDefinitionPrefix' but does not define an anchor.", path)

public class ForbiddenAnchorOrAliasException(message: String, path: YamlPath) : YamlException(message, path)
19 changes: 17 additions & 2 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1170,8 1170,23 @@ class YamlReadingTest : DescribeSpec({
name: *name
""".trimIndent()

context("parsing that input") {
val configuration = YamlConfiguration(extensionDefinitionPrefix = ".")
context("parsing anchors and aliases is disabled") {
val configuration = YamlConfiguration(extensionDefinitionPrefix = ".", allowAnchorsAndAliases = false)
val yaml = Yaml(configuration = configuration)

it("throws an appropriate exception") {
val exception = shouldThrow<ForbiddenAnchorOrAliasException> { yaml.decodeFromString(SimpleStructure.serializer(), input) }

exception.asClue {
it.message shouldBe "Parsing anchors and aliases is disabled."
it.line shouldBe 1
it.column shouldBe 18
}
}
}

context("parsing anchors and aliases is enabled") {
val configuration = YamlConfiguration(extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true)
val yaml = Yaml(configuration = configuration)
val result = yaml.decodeFromString(SimpleStructure.serializer(), input)

Expand Down
2 changes: 1 addition & 1 deletion src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 60,7 @@ public actual class Yaml(

private fun parseToYamlNodeFromReader(source: Reader): YamlNode {
val parser = YamlParser(source)
val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix)
val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix, configuration.allowAnchorsAndAliases)
val node = reader.read()
parser.ensureEndOfStreamReached()
return node
Expand Down
9 changes: 9 additions & 0 deletions src/jvmMain/kotlin/com/charleskorn/kaml/YamlNodeReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 30,7 @@ import java.util.Optional
internal actual class YamlNodeReader(
private val parser: YamlParser,
private val extensionDefinitionPrefix: String? = null,
private val allowAnchorsAndAliases: Boolean = false,
) {
private val aliases = mutableMapOf<Anchor, YamlNode>()

Expand All @@ -43,6 44,10 @@ internal actual class YamlNodeReader(

if (event is NodeEvent) {
event.anchor.ifPresent {
if (!allowAnchorsAndAliases) {
throw ForbiddenAnchorOrAliasException("Parsing anchors and aliases is disabled.", path)
}

aliases.put(it, node.withPath(YamlPath.forAliasDefinition(it.value, event.location)))
}

Expand Down Expand Up @@ -186,6 191,10 @@ internal actual class YamlNodeReader(
}

private fun readAlias(event: AliasEvent, path: YamlPath): YamlNode {
if (!allowAnchorsAndAliases) {
throw ForbiddenAnchorOrAliasException("Parsing anchors and aliases is disabled.", path)
}

val anchor = event.anchor.get()

val resolvedNode = aliases.getOrElse(anchor) {
Expand Down
37 changes: 26 additions & 11 deletions src/jvmTest/kotlin/com/charleskorn/kaml/YamlNodeReaderTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 312,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()

it("returns the expected list") {
result shouldBe
Expand All @@ -339,7 339,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()

it("returns the expected list, using the most-recently defined value each time the alias is referenced") {
result shouldBe
Expand All @@ -363,11 363,11 @@ class YamlNodeReaderTest : DescribeSpec({
- *thing
""".trimIndent()

describe("parsing that input") {
describe("parsing that input with anchor and alias parsing enabled") {
it("throws an appropriate exception") {
val exception = shouldThrow<UnknownAnchorException> {
val parser = YamlParser(input)
YamlNodeReader(parser).read()
YamlNodeReader(parser, allowAnchorsAndAliases = true).read()
}

exception.asClue {
Expand All @@ -378,6 378,21 @@ class YamlNodeReaderTest : DescribeSpec({
}
}
}

describe("parsing that input with anchor and alias parsing disabled") {
it("throws an appropriate exception") {
val exception = shouldThrow<ForbiddenAnchorOrAliasException> {
val parser = YamlParser(input)
YamlNodeReader(parser, allowAnchorsAndAliases = false).read()
}

exception.asClue {
it.message shouldBe "Parsing anchors and aliases is disabled."
it.line shouldBe 2
it.column shouldBe 3
}
}
}
}

context("given some input representing a list of strings in flow style") {
Expand Down Expand Up @@ -652,7 667,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()
val key1Path = YamlPath.root.withMapElementKey("key1", Location(1, 1))
val value1Path = key1Path.withMapElementValue(Location(1, 7))
val key2Path = YamlPath.root.withMapElementKey("key2", Location(2, 1))
Expand Down Expand Up @@ -1156,7 1171,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()

val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3))
val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13))
Expand Down Expand Up @@ -1206,7 1221,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()

val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3))
val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13))
Expand Down Expand Up @@ -1302,7 1317,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()

val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3))
val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13))
Expand Down Expand Up @@ -1365,7 1380,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser).read()
val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read()

val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3))
val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 11))
Expand Down Expand Up @@ -1531,7 1546,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input with an extension definition prefix defined") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".").read()
val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true).read()

val fooKeyPath = YamlPath.root.withMapElementKey("foo", Location(3, 1))
val fooValuePath = fooKeyPath.withMapElementValue(Location(4, 5))
Expand Down Expand Up @@ -1598,7 1613,7 @@ class YamlNodeReaderTest : DescribeSpec({

describe("parsing that input with an extension definition prefix defined") {
val parser = YamlParser(input)
val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".").read()
val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true).read()

val keyPath = YamlPath.root
.withMerge(Location(4, 6))
Expand Down

0 comments on commit 5f82a2d

Please sign in to comment.