From cd91cf3c99123bd1e53fd6a1de0e3d1922f05bb2 Mon Sep 17 00:00:00 2001
From: Norman Maurer <norman_maurer@apple.com>
Date: Mon, 12 Dec 2022 14:04:31 +0100
Subject: [PATCH] Merge pull request from GHSA-fx2c-96vj-985v

Motivation:

A StackOverflowError can be raised when parsing a malformed crafted message due to an
infinite recursion. We should bail out early

Modifications:

- Add a limit to the maximum nesting of TLV.
- Add unit test

Result:

Not possible anymore to trigger a StackOverflowError with a special crafted packet

---
 .../handler/codec/haproxy/HAProxyMessage.java | 16 +++--
 .../haproxy/HAProxyMessageDecoderTest.java    | 65 +++++++++++++++++++
 2 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java b/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java
index b40bf42..3f30c4a 100644
--- a/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java
+++ b/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java
@@ -30,6 +30,9 @@ import java.util.List;
  */
 public final class HAProxyMessage {
 
+    // Let's pick some conservative limit here.
+    private static final int MAX_NESTING_LEVEL = 128;
+
     /**
      * Version 1 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
      * 'UNKNOWN' we must discard all other header values.
@@ -238,7 +241,7 @@ public final class HAProxyMessage {
     }
 
     private static List<HAProxyTLV> readTlvs(final ByteBuf header) {
-        HAProxyTLV haProxyTLV = readNextTLV(header);
+        HAProxyTLV haProxyTLV = readNextTLV(header, 0);
         if (haProxyTLV == null) {
             return Collections.emptyList();
         }
@@ -250,12 +253,15 @@ public final class HAProxyMessage {
             if (haProxyTLV instanceof HAProxySSLTLV) {
                 haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs());
             }
-        } while ((haProxyTLV = readNextTLV(header)) != null);
+        } while ((haProxyTLV = readNextTLV(header, 0)) != null);
         return haProxyTLVs;
     }
 
-    private static HAProxyTLV readNextTLV(final ByteBuf header) {
-
+    private static HAProxyTLV readNextTLV(final ByteBuf header, int nestingLevel) {
+        if (nestingLevel > MAX_NESTING_LEVEL) {
+            throw new HAProxyProtocolException(
+                    "Maximum TLV nesting level reached: " + nestingLevel + " (expected: < " + MAX_NESTING_LEVEL + ')');
+        }
         // We need at least 4 bytes for a TLV
         if (header.readableBytes() < 4) {
             return null;
@@ -276,7 +282,7 @@ public final class HAProxyMessage {
 
                 final List<HAProxyTLV> encapsulatedTlvs = new ArrayList<HAProxyTLV>(4);
                 do {
-                    final HAProxyTLV haProxyTLV = readNextTLV(byteBuf);
+                    final HAProxyTLV haProxyTLV = readNextTLV(byteBuf, nestingLevel + 1);
                     if (haProxyTLV == null) {
                         break;
                     }
diff --git a/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java b/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java
index 2d4039d..8d27e4a 100644
--- a/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java
+++ b/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java
@@ -16,6 +16,7 @@
 package io.netty.handler.codec.haproxy;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.embedded.EmbeddedChannel;
 import io.netty.handler.codec.ProtocolDetectionResult;
@@ -26,6 +27,9 @@ import io.netty.util.CharsetUtil;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.List;
 
 import static io.netty.buffer.Unpooled.*;
@@ -1013,4 +1017,65 @@ public class HAProxyMessageDecoderTest {
         assertNull(result.detectedProtocol());
         incompleteHeader.release();
     }
+
+    @Test
+    public void testNestedTLV() throws Exception {
+        ByteArrayOutputStream headerWriter = new ByteArrayOutputStream();
+        //src_ip = "AAAA", dst_ip = "BBBB", src_port = "CC", dst_port = "DD"
+        headerWriter.write(new byte[] {'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', 'D', 'D'});
+        //write TLVs
+        int countOfTLVs = 8100;
+        ByteBuffer tlvLengthBuf = ByteBuffer.allocate(2);
+        tlvLengthBuf.order(ByteOrder.BIG_ENDIAN);
+        short totalLength = (short) (countOfTLVs * (1 + 2 + 1 + 4));
+        for (int i = 0; i < countOfTLVs; i++) {
+            //write PP2_TYPE_SSL TLV
+            headerWriter.write(0x20); //PP2_TYPE_SSL
+            //notice that the TLV length cannot be bigger than 0xffff
+            totalLength -= 1 + 2; //exclude type and length themselves
+            tlvLengthBuf.clear();
+            tlvLengthBuf.putShort(totalLength);
+            //add to the header
+            headerWriter.write(tlvLengthBuf.array());
+            //write client field
+            headerWriter.write(1);
+            //write verify field
+            headerWriter.write(new byte[] {'V', 'V', 'V', 'V'});
+            //subtract the client and verify fields
+            totalLength -= 1 + 4;
+        }
+        byte[] header = headerWriter.toByteArray();
+        ByteBuffer numsWrite = ByteBuffer.allocate(2);
+        numsWrite.order(ByteOrder.BIG_ENDIAN);
+        numsWrite.putShort((short) header.length);
+
+        final  ByteBuf data = Unpooled.buffer();
+        data.writeBytes(new byte[] {
+                (byte) 0x0D,
+                (byte) 0x0A,
+                (byte) 0x0D,
+                (byte) 0x0A,
+                (byte) 0x00,
+                (byte) 0x0D,
+                (byte) 0x0A,
+                (byte) 0x51,
+                (byte) 0x55,
+                (byte) 0x49,
+                (byte) 0x54,
+                (byte) 0x0A
+        });
+        //verCmd = 32
+        byte versionCmd = 0x20 | 1; //V2 | ProxyCmd
+        data.writeByte(versionCmd);
+        data.writeByte(17); //TPAF_TCP4_BYTE
+        data.writeBytes(numsWrite.array());
+        data.writeBytes(header);
+
+        assertThrows(HAProxyProtocolException.class, new Executable() {
+            @Override
+            public void execute() {
+                ch.writeInbound(data);
+            }
+        });
+    }
 }
-- 
2.30.0

