CVE-2023-3635: Incorrect Conversion between Numeric Types (CWE-681)
value: GzipSource does not handle an exception that might be raised when parsing a malformed gzip buffer. This may lead to denial of service of the Okio client when handling a crafted GZIP archive, by using the GzipSource class.


CWE description: When converting from one data type to another, such as long to integer, data can be omitted or translated in a way that produces unexpected values. If the resulting values are used in a sensitive context, then dangerous behaviors may occur.
CVSS v3 base score: 7.5
Published: 2023-07-12T19:15Z
Repository: https://github.com/square/okio
Language: Kotlin
Files changed: okio/src/jvmMain/kotlin/okio/GzipSource.kt, okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
Commit date: 2023-06-22 09:22:45 -0400
Commit message: Fix a bug where xlen larger than 0x7fff was rejected (#1280)

We treated this short value as unsigned and it should have been
treated as signed.
Diff: diff --git a/okio/src/jvmMain/kotlin/okio/GzipSource.kt b/okio/src/jvmMain/kotlin/okio/GzipSource.kt
index ff1e3d3..1cc4172 100644
--- a/okio/src/jvmMain/kotlin/okio/GzipSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/GzipSource.kt
@@ -115,11 +115,11 @@ class GzipSource(source: Source) : Source {
     // | XLEN  |...XLEN bytes of "extra field"...| (more-->)
     // +---+---+=================================+
     if (flags.getBit(FEXTRA)) {
       source.require(2)
       if (fhcrc) updateCrc(source.buffer, 0, 2)
-      val xlen = source.buffer.readShortLe().toLong()
+      val xlen = (source.buffer.readShortLe().toInt() and 0xffff).toLong()
       source.require(xlen)
       if (fhcrc) updateCrc(source.buffer, 0, xlen)
       source.skip(xlen)
     }
 
diff --git a/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
index e6b407b..dfe7182 100644
--- a/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
@@ -21,16 +21,30 @@ import okio.ByteString.Companion.decodeHex
 import org.junit.Test
 
 class GzipKotlinTest {
   @Test fun sink() {
     val data = Buffer()
-    val gzip = (data as Sink).gzip()
-    gzip.buffer().writeUtf8("Hi!").close()
+    (data as Sink).gzip().buffer().use { gzip ->
+      gzip.writeUtf8("Hi!")
+    }
     assertEquals("1f8b0800000000000000f3c8540400dac59e7903000000", data.readByteString().hex())
   }
 
   @Test fun source() {
     val buffer = Buffer().write("1f8b0800000000000000f3c8540400dac59e7903000000".decodeHex())
-    val gzip = (buffer as Source).gzip()
-    assertEquals("Hi!", gzip.buffer().readUtf8())
+    (buffer as Source).gzip().buffer().use { gzip ->
+      assertEquals("Hi!", gzip.readUtf8())
+    }
+  }
+
+  @Test fun extraLongXlen() {
+    val xlen = 0xffff
+    val buffer = Buffer()
+      .write("1f8b0804000000000000".decodeHex())
+      .writeShort(xlen)
+      .write(ByteArray(xlen))
+      .write("f3c8540400dac59e7903000000".decodeHex())
+    (buffer as Source).gzip().buffer().use { gzip ->
+      assertEquals("Hi!", gzip.readUtf8())
+    }
   }
 }

Vulnerable code:       val xlen = source.buffer.readShortLe().toLong()
    val gzip = (data as Sink).gzip()
    gzip.buffer().writeUtf8("Hi!").close()
    val gzip = (buffer as Source).gzip()
    assertEquals("Hi!", gzip.buffer().readUtf8())
Fixed code:       val xlen = (source.buffer.readShortLe().toInt() and 0xffff).toLong()
    (data as Sink).gzip().buffer().use { gzip ->
      gzip.writeUtf8("Hi!")
    }
    (buffer as Source).gzip().buffer().use { gzip ->
      assertEquals("Hi!", gzip.readUtf8())
    }
  }

  @Test fun extraLongXlen() {
    val xlen = 0xffff
    val buffer = Buffer()
      .write("1f8b0804000000000000".decodeHex())
      .writeShort(xlen)
      .write(ByteArray(xlen))
      .write("f3c8540400dac59e7903000000".decodeHex())
    (buffer as Source).gzip().buffer().use { gzip ->
      assertEquals("Hi!", gzip.readUtf8())
    }