Skip to content

Commit b938a8c

Browse files
authored
Reject overflowing HPACK integers (#9401)
Decode HPACK integers in Long and reject values that would overflow Int. This prevents malformed header indexes, dynamic table size updates, and string lengths from wrapping into negative or attacker-chosen small positive values.
1 parent 6f053fe commit b938a8c

2 files changed

Lines changed: 59 additions & 11 deletions

File tree

  • okhttp/src
    • commonJvmAndroid/kotlin/okhttp3/internal/http2
    • jvmTest/kotlin/okhttp3/internal/http2

okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Hpack.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -365,19 +365,25 @@ object Hpack {
365365
}
366366

367367
// This is a multibyte value. Read 7 bits at a time.
368-
var result = prefixMask
368+
var result = prefixMask.toLong()
369369
var shift = 0
370+
var byteCount = 0
370371
while (true) {
372+
// An Int.MAX_VALUE payload needs at most 5 continuation bytes after the prefix.
373+
if (byteCount == 5) {
374+
throw IOException("HPACK integer overflow")
375+
}
371376
val b = readByte()
372-
if (b and 0x80 != 0) { // Equivalent to (b >= 128) since b is in [0..255].
373-
result += b and 0x7f shl shift
374-
shift += 7
375-
} else {
376-
result += b shl shift // Last byte.
377-
break
377+
byteCount++
378+
val increment = (b and 0x7f).toLong() shl shift
379+
if (increment > Int.MAX_VALUE.toLong() - result) {
380+
throw IOException("HPACK integer overflow")
378381
}
382+
result += increment
383+
if (b and 0x80 == 0) break
384+
shift += 7
379385
}
380-
return result
386+
return result.toInt()
381387
}
382388

383389
/** Reads a potentially Huffman encoded byte string. */

okhttp/src/jvmTest/kotlin/okhttp3/internal/http2/HpackTest.kt

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ class HpackTest {
375375
assertFailsWith<IOException> {
376376
hpackReader!!.readHeaders()
377377
}.also { expected ->
378-
assertThat(expected.message).isEqualTo("Header index too large -2147483521")
378+
assertThat(expected.message).isEqualTo("HPACK integer overflow")
379379
}
380380
}
381381

@@ -413,8 +413,18 @@ class HpackTest {
413413
assertFailsWith<IOException> {
414414
hpackReader!!.readHeaders()
415415
}.also { expected ->
416-
assertThat(expected.message)
417-
.isEqualTo("Invalid dynamic table size update -2147483648")
416+
assertThat(expected.message).isEqualTo("HPACK integer overflow")
417+
}
418+
}
419+
420+
@Test
421+
fun dynamicTableSizeUpdateRejectsWrappedSmallPositive() {
422+
bytesIn.writeByte(0x3f)
423+
bytesIn.write("ffffffff8f00".decodeHex())
424+
assertFailsWith<IOException> {
425+
hpackReader!!.readHeaders()
426+
}.also { expected ->
427+
assertThat(expected.message).isEqualTo("HPACK integer overflow")
418428
}
419429
}
420430

@@ -823,6 +833,38 @@ class HpackTest {
823833
.isEqualTo(0x7fffffff)
824834
}
825835

836+
@Test
837+
fun overflowingValueRejected() {
838+
assertFailsWith<IOException> {
839+
newReader(Buffer().write("8080808008".decodeHex())).readInt(0xff, 0x7f)
840+
}.also { expected ->
841+
assertThat(expected.message).isEqualTo("HPACK integer overflow")
842+
}
843+
}
844+
845+
@Test
846+
fun tooManyContinuationBytesRejected() {
847+
assertFailsWith<IOException> {
848+
newReader(Buffer().write("808080808001".decodeHex())).readInt(0x1f, 0x1f)
849+
}.also { expected ->
850+
assertThat(expected.message).isEqualTo("HPACK integer overflow")
851+
}
852+
}
853+
854+
@Test
855+
fun stringLengthOverflowRejected() {
856+
val source = Buffer()
857+
source.writeByte(0x00)
858+
source.writeByte(0x7f)
859+
source.write("8080808008".decodeHex())
860+
861+
assertFailsWith<IOException> {
862+
newReader(source).readHeaders()
863+
}.also { expected ->
864+
assertThat(expected.message).isEqualTo("HPACK integer overflow")
865+
}
866+
}
867+
826868
@Test
827869
fun prefixMask() {
828870
hpackWriter!!.writeInt(31, 31, 0)

0 commit comments

Comments
 (0)