From 28fc8a4f3bda4a7752cf1d4fbd588da3b2acb1e4 Mon Sep 17 00:00:00 2001
From: Emmanuel Thompson <eet6646@gmail.com>
Date: Tue, 28 Apr 2020 14:03:26 -0400
Subject: [PATCH] detect/asn1: Fix relative_offset keyword option

- Fix relative_offset keyword option to be relative in regards to the
last content match
- Change relative_offset to int32_t with bounds check to allow the full
range of the packet buffer size (uint16_t)
- Added checks for over/underflows
- Changed the offset type to uint16_t because the offset is applied to
the payload length, which is a uint16_t
- Adjusted test cases to work relative to the content match
- Added test case to verify bounds
---
 src/detect-asn1.c | 95 ++++++++++++++++++++++++++++++++++++++++++-----
 src/detect-asn1.h |  3 +-
 2 files changed, 86 insertions(+), 12 deletions(-)

diff --git a/src/detect-asn1.c b/src/detect-asn1.c
index 2b51e9f000c7..f0c2ab3dd351 100644
--- a/src/detect-asn1.c
+++ b/src/detect-asn1.c
@@ -148,15 +148,28 @@ static int DetectAsn1Match(DetectEngineThreadCtx *det_ctx, Packet *p,
     }
 
     const DetectAsn1Data *ad = (const DetectAsn1Data *)ctx;
-    int32_t offset;
+    uint16_t offset;
     if (ad->flags & ASN1_ABSOLUTE_OFFSET) {
         offset = ad->absolute_offset;
     } else if (ad->flags & ASN1_RELATIVE_OFFSET) {
-        offset = ad->relative_offset;
+        // relative offset in regards to the last content match
+
+        // This range check is done when relative_offset
+        BUG_ON(ad->relative_offset > UINT16_MAX || ad->relative_offset < -UINT16_MAX);
+
+        int64_t tmp_offset = det_ctx->buffer_offset + ad->relative_offset;
+
+        // check for under/overflow before downcasting
+        if (tmp_offset < 0 || tmp_offset > UINT16_MAX) {
+            return 0;
+        }
+
+        offset = (uint16_t)tmp_offset;
     } else {
         offset = 0;
     }
-    if (offset >= (int32_t)p->payload_len) {
+
+    if (offset >= p->payload_len) {
         return 0;
     }
 
@@ -204,7 +217,7 @@ static DetectAsn1Data *DetectAsn1Parse(const char *instr)
     DetectAsn1Data *fd = NULL;
     char *tok = NULL;
     uint32_t ov_len = 0;
-    uint32_t abs_off = 0;
+    uint16_t abs_off = 0;
     int32_t rel_off = 0;
     uint8_t flags = 0;
     char *saveptr = NULL;
@@ -244,7 +257,7 @@ static DetectAsn1Data *DetectAsn1Parse(const char *instr)
             /* get the param */
             tok = strtok_r(NULL, ASN_DELIM, &saveptr);
             if (tok == NULL ||
-                StringParseUint32(&abs_off, 10, 0, tok) <= 0)
+                StringParseUint16(&abs_off, 10, 0, tok) <= 0)
             {
                 SCLogError(SC_ERR_INVALID_VALUE, "Malformed value for "
                            "absolute_offset: %s", tok);
@@ -254,8 +267,11 @@ static DetectAsn1Data *DetectAsn1Parse(const char *instr)
             flags |= ASN1_RELATIVE_OFFSET;
             /* get the param */
             tok = strtok_r(NULL, ASN_DELIM, &saveptr);
+
+            // Range check on uint16_t max as uint16_t is the type of the buffer
+            // the offset is relative to
             if (tok == NULL ||
-                StringParseInt32(&rel_off, 10, 0, tok) <= 0)
+                StringParseI32RangeCheck(&rel_off, 10, 0, tok, -UINT16_MAX, UINT16_MAX) <= 0)
             {
                 SCLogError(SC_ERR_INVALID_VALUE, "Malformed value for "
                            "relative_offset: %s", tok);
@@ -1092,7 +1108,7 @@ static int DetectAsn1TestReal01(void)
              "content:\"Pablo\"; asn1:absolute_offset 0, "
              "oversize_length 130; sid:1;)";
     sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
-             "content:\"AA\"; asn1:relative_offset 2, "
+             "content:\"AA\"; asn1:relative_offset 0, "
              "oversize_length 130; sid:2;)";
     sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
              "content:\"lalala\"; asn1: oversize_length 2000; sid:3;)";
@@ -1171,7 +1187,7 @@ static int DetectAsn1TestReal02(void)
              "content:\"Pablo\"; asn1:absolute_offset 0, "
              "oversize_length 140; sid:1;)";
     sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
-             "content:\"AA\"; asn1:relative_offset 2, "
+             "content:\"AA\"; asn1:relative_offset 0, "
              "oversize_length 140; sid:2;)";
     sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
              "content:\"lalala\"; asn1: oversize_length 2000; sid:3;)";
@@ -1252,7 +1268,7 @@ static int DetectAsn1TestReal03(void)
 /**
  * \test DetectAsn1TestReal04 like the real test 02, but modified the
  *       relative offset to check negative offset values, in this case
- *       start decoding from -7 bytes respect the content match "John"
+ *       start decoding from -11 bytes respect the content match "John"
  */
 static int DetectAsn1TestReal04(void)
 {
@@ -1309,7 +1325,7 @@ static int DetectAsn1TestReal04(void)
              "content:\"Pablo\"; asn1:absolute_offset 0, "
              "oversize_length 140; sid:1;)";
     sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
-             "content:\"John\"; asn1:relative_offset -7, "
+             "content:\"John\"; asn1:relative_offset -11, "
              "oversize_length 140; sid:2;)";
     sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
              "content:\"lalala\"; asn1: oversize_length 2000; sid:3;)";
@@ -1328,6 +1344,64 @@ static int DetectAsn1TestReal04(void)
     return result;
 }
 
+/**
+ * \test DetectAsn1TestReal05 like the real test 04, but modified the
+ *       relative offset to check offset values which could read past the bounds
+ *       of the buffer
+ */
+static int DetectAsn1TestReal05(void)
+{
+    int result = 0;
+    /* Check the start with AA (this is to test the relative_offset keyword) */
+    uint8_t *buf = (uint8_t *) "AA\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
+                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
+                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
+                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
+                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
+                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
+                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
+                   "Jones""\xA0\x0A\x43\x08""19590717"
+                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
+                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
+                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
+                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
+                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
+                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
+                   "\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05""Jones"
+                   "\xA0\x0A\x43\x08""19590717";
+
+    uint16_t buflen = strlen((char *)buf) - 1;
+
+    Packet *p[1];
+
+    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
+
+    if (p[0] == NULL)
+        goto end;
+
+    const char *sigs[3];
+    sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; "
+             "content:\"John\"; asn1:relative_offset -100, "
+             "oversize_length 132; sid:1;)";
+    sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
+             "content:\"John\"; asn1:relative_offset -11, "
+             "oversize_length 132; sid:2;)";
+    sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
+             "content:\"John\"; asn1:  relative_offset 5000, "
+             "oversize_length 132; sid:3;)";
+
+    uint32_t sid[3] = {1, 2, 3};
+
+    // The second signature should match
+    uint32_t results[1][3] = {{0, 1, 0}};
+
+    result = UTHGenericTest(p, 1, sigs, sid, (uint32_t *) results, 3);
+
+    UTHFreePackets(p, 1);
+end:
+    return result;
+}
+
 #endif /* UNITTESTS */
 
 /**
@@ -1364,6 +1438,7 @@ static void DetectAsn1RegisterTests(void)
     UtRegisterTest("DetectAsn1TestReal02", DetectAsn1TestReal02);
     UtRegisterTest("DetectAsn1TestReal03", DetectAsn1TestReal03);
     UtRegisterTest("DetectAsn1TestReal04", DetectAsn1TestReal04);
+    UtRegisterTest("DetectAsn1TestReal05", DetectAsn1TestReal05);
 
 #endif /* UNITTESTS */
 }
diff --git a/src/detect-asn1.h b/src/detect-asn1.h
index c38d6439e729..9f350e25b131 100644
--- a/src/detect-asn1.h
+++ b/src/detect-asn1.h
@@ -36,7 +36,7 @@
 typedef struct DetectAsn1Data_ {
     uint8_t flags;     /* flags indicating the checks loaded */
     uint32_t oversize_length;   /* Length argument if needed */
-    int32_t absolute_offset;   /* Length argument if needed */
+    uint16_t absolute_offset;   /* Length argument if needed */
     int32_t relative_offset;   /* Length argument if needed */
 } DetectAsn1Data;
 
@@ -44,4 +44,3 @@ typedef struct DetectAsn1Data_ {
 void DetectAsn1Register (void);
 
 #endif /* __DETECT_ASN1_H__ */
-