]> The Tcpdump Group git mirrors - tcpdump/blobdiff - print-resp.c
CI: Add warning exemptions for Sun C (suncc-5.14) on Solaris 10
[tcpdump] / print-resp.c
index 5e36edafdae5398e69f48292238d42c8ef77674e..1cefbaccda53e3ba1ab191f9d43c2ed21e3fb157 100644 (file)
@@ -1,7 +1,4 @@
 /*
- * This file implements decoding of the REdis Serialization Protocol.
- *
- *
  * Copyright (c) 2015 The TCPDUMP project
  * All rights reserved.
  *
  * Initial contribution by Andrew Darqui ([email protected]).
  */
 
-#define NETDISSECT_REWORKED
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
+/* \summary: REdis Serialization Protocol (RESP) printer */
+
+#include <config.h>
 
-#include <netdissect-stdinc.h>
+#include "netdissect-stdinc.h"
 #include "netdissect.h"
 #include <limits.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
 
 #include "extract.h"
 
-static const char tstr[] = " [|RESP]";
 
 /*
- * For information regarding RESP, see: http://redis.io/topics/protocol
+ * For information regarding RESP, see: https://redis.io/topics/protocol
  */
 
 #define RESP_SIMPLE_STRING    '+'
@@ -56,73 +48,142 @@ static const char tstr[] = " [|RESP]";
 #define RESP_BULK_STRING      '$'
 #define RESP_ARRAY            '*'
 
-#define resp_print_empty(ndo)      ND_PRINT((ndo, " empty"))
-#define resp_print_null(ndo)       ND_PRINT((ndo, " null"))
-#define resp_print_invalid(ndo)    ND_PRINT((ndo, " invalid"))
+#define resp_print_empty(ndo)            ND_PRINT(" empty")
+#define resp_print_null(ndo)             ND_PRINT(" null")
+#define resp_print_length_too_large(ndo) ND_PRINT(" length too large")
+#define resp_print_length_negative(ndo)  ND_PRINT(" length negative and not -1")
+#define resp_print_invalid(ndo)          ND_PRINT(" invalid")
+
+static int resp_parse(netdissect_options *, const u_char *, int);
+static int resp_print_string_error_integer(netdissect_options *, const u_char *, int);
+static int resp_print_simple_string(netdissect_options *, const u_char *, int);
+static int resp_print_integer(netdissect_options *, const u_char *, int);
+static int resp_print_error(netdissect_options *, const u_char *, int);
+static int resp_print_bulk_string(netdissect_options *, const u_char *, int);
+static int resp_print_bulk_array(netdissect_options *, const u_char *, int);
+static int resp_print_inline(netdissect_options *, const u_char *, int);
+static int resp_get_length(netdissect_options *, const u_char *, int, const u_char **);
+
+#define LCHECK2(_tot_len, _len) \
+    {                           \
+        if (_tot_len < _len)    \
+            goto trunc;         \
+    }
 
-void       resp_print(netdissect_options *, const u_char *, u_int);
-static int resp_parse(netdissect_options *, register const u_char *, int);
-static int resp_print_string_error_integer(netdissect_options *, register const u_char *, int);
-static int resp_print_simple_string(netdissect_options *, register const u_char *, int);
-static int resp_print_integer(netdissect_options *, register const u_char *, int);
-static int resp_print_error(netdissect_options *, register const u_char *, int);
-static int resp_print_bulk_string(netdissect_options *, register const u_char *, int);
-static int resp_print_bulk_array(netdissect_options *, register const u_char *, int);
-static int resp_print_inline(netdissect_options *, register const u_char *, int);
+#define LCHECK(_tot_len) LCHECK2(_tot_len, 1)
 
 /*
- * MOVE_FORWARD:
+ * FIND_CRLF:
  * Attempts to move our 'ptr' forward until a \r\n is found,
- * while also making sure we don't exceed the buffer 'len'.
- * If we exceed, jump to trunc.
+ * while also making sure we don't exceed the buffer '_len'
+ * or go past the end of the captured data.
+ * If we exceed or go past the end of the captured data,
+ * jump to trunc.
  */
-#define MOVE_FORWARD(ptr, len) \
-    while(*ptr != '\r' && *(ptr+1) != '\n') { ND_TCHECK2(*ptr, 2); ptr++; len--; }
+#define FIND_CRLF(_ptr, _len)                   \
+    for (;;) {                                  \
+        LCHECK2(_len, 2);                       \
+        ND_TCHECK_2(_ptr);                      \
+        if (GET_U_1(_ptr) == '\r' &&            \
+            GET_U_1(_ptr+1) == '\n')            \
+            break;                              \
+        _ptr++;                                 \
+        _len--;                                 \
+    }
 
 /*
- * MOVE_FORWARD_CR_OR_LF
- * Attempts to move our 'ptr' forward until a \r or \n is found,
- * while also making sure we don't exceed the buffer 'len'.
- * If we exceed, jump to trunc.
+ * CONSUME_CRLF
+ * Consume a CRLF that we've just found.
  */
-#define MOVE_FORWARD_CR_OR_LF(ptr, len) \
-    while(*ptr != '\r' && *ptr != '\n') { ND_TCHECK(*ptr); ptr++; len--; }
+#define CONSUME_CRLF(_ptr, _len) \
+    _ptr += 2;                   \
+    _len -= 2;
 
 /*
- * CONSUME_CR_OR_LF
- * Consume all consecutive \r and \n bytes.
- * If we exceed 'len', jump to trunc.
+ * FIND_CR_OR_LF
+ * Attempts to move our '_ptr' forward until a \r or \n is found,
+ * while also making sure we don't exceed the buffer '_len'
+ * or go past the end of the captured data.
+ * If we exceed or go past the end of the captured data,
+ * jump to trunc.
  */
-#define CONSUME_CR_OR_LF(ptr, len) \
-    while (*ptr == '\r' || *ptr == '\n') { ND_TCHECK(*ptr); ptr++; len--; }
+#define FIND_CR_OR_LF(_ptr, _len)           \
+    for (;;) {                              \
+        LCHECK(_len);                       \
+        if (GET_U_1(_ptr) == '\r' ||        \
+            GET_U_1(_ptr) == '\n')          \
+            break;                          \
+        _ptr++;                             \
+        _len--;                             \
+    }
 
 /*
- * INCBY
- * Attempts to increment our 'ptr' by 'increment' bytes.
- * If our increment exceeds the buffer length (len - increment),
- * bail out by jumping to the trunc goto tag.
+ * CONSUME_CR_OR_LF
+ * Consume all consecutive \r and \n bytes.
+ * If we exceed '_len' or go past the end of the captured data,
+ * jump to trunc.
  */
-#define INCBY(ptr, increment, len) \
-    { ND_TCHECK2(*ptr, increment); ptr+=increment; len-=increment; }
+#define CONSUME_CR_OR_LF(_ptr, _len)             \
+    {                                            \
+        int _found_cr_or_lf = 0;                 \
+        for (;;) {                               \
+            /*                                   \
+             * Have we hit the end of data?      \
+             */                                  \
+            if (_len == 0 || !ND_TTEST_1(_ptr)) {\
+                /*                               \
+                 * Yes.  Have we seen a \r       \
+                 * or \n?                        \
+                 */                              \
+                if (_found_cr_or_lf) {           \
+                    /*                           \
+                     * Yes.  Just stop.          \
+                     */                          \
+                    break;                       \
+                }                                \
+                /*                               \
+                 * No.  We ran out of packet.    \
+                 */                              \
+                goto trunc;                      \
+            }                                    \
+            if (GET_U_1(_ptr) != '\r' &&         \
+                GET_U_1(_ptr) != '\n')           \
+                break;                           \
+            _found_cr_or_lf = 1;                 \
+            _ptr++;                              \
+            _len--;                              \
+        }                                        \
+    }
 
 /*
- * INC1
- * Increment our ptr by 1 byte.
- * Most often used to skip an opcode (+-:*$ etc)
+ * SKIP_OPCODE
+ * Skip over the opcode character.
+ * The opcode has already been fetched, so we know it's there, and don't
+ * need to do any checks.
  */
-#define INC1(ptr, len) INCBY(ptr, 1, len)
+#define SKIP_OPCODE(_ptr, _tot_len) \
+    _ptr++;                         \
+    _tot_len--;
 
 /*
- * INC2
- * Increment our ptr by 2 bytes.
- * Most often used to skip CRLF (\r\n).
+ * GET_LENGTH
+ * Get a bulk string or array length.
  */
-#define INC2(ptr, len) INCBY(ptr, 2, len)
+#define GET_LENGTH(_ndo, _tot_len, _ptr, _len)                \
+    {                                                         \
+        const u_char *_endp;                                  \
+        _len = resp_get_length(_ndo, _ptr, _tot_len, &_endp); \
+        _tot_len -= (_endp - _ptr);                           \
+        _ptr = _endp;                                         \
+    }
 
 /*
  * TEST_RET_LEN
  * If ret_len is < 0, jump to the trunc tag which returns (-1)
  * and 'bubbles up' to printing tstr. Otherwise, return ret_len.
+ *
+ * Note that using this macro with a semicolon at the end emits a warning from
+ * Sun C about an unreachable statement (the semicolon is the statement).
  */
 #define TEST_RET_LEN(rl) \
     if (rl < 0) { goto trunc; } else { return rl; }
@@ -138,23 +199,23 @@ static int resp_print_inline(netdissect_options *, register const u_char *, int)
 /*
  * RESP_PRINT_SEGMENT
  * Prints a segment in the form of: ' "<stuff>"\n"
+ * Assumes the data has already been verified as present.
  */
 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len)            \
-        ND_PRINT((_ndo, " \""));                       \
-        if (fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \
-               goto trunc; \
-        fn_print_char(_ndo, '"');
+    ND_PRINT(" \"");                                   \
+    if (nd_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \
+        goto trunc;                                    \
+    ND_PRINT("\"");
 
 void
 resp_print(netdissect_options *ndo, const u_char *bp, u_int length)
 {
-    int ret_len = 0, length_cur = length;
+    int ret_len = 0;
 
-    if(!bp || length <= 0)
-        return;
+    ndo->ndo_protocol = "resp";
 
-    ND_PRINT((ndo, ": RESP"));
-    while (length_cur > 0) {
+    ND_PRINT(": RESP");
+    while (length != 0) {
         /*
          * This block supports redis pipelining.
          * For example, multiple operations can be pipelined within the same string:
@@ -164,26 +225,28 @@ resp_print(netdissect_options *ndo, const u_char *bp, u_int length)
          * In order to handle this case, we must try and parse 'bp' until
          * 'length' bytes have been processed or we reach a trunc condition.
          */
-        ret_len = resp_parse(ndo, bp, length_cur);
+        ret_len = resp_parse(ndo, bp, length);
         TEST_RET_LEN_NORETURN(ret_len);
         bp += ret_len;
-        length_cur -= ret_len;
+        length -= ret_len;
     }
 
     return;
 
 trunc:
-    ND_PRINT((ndo, "%s", tstr));
+    nd_print_trunc(ndo);
 }
 
 static int
-resp_parse(netdissect_options *ndo, register const u_char *bp, int length)
+resp_parse(netdissect_options *ndo, const u_char *bp, int length)
 {
-    int ret_len = 0;
-    u_char op = *bp;
+    u_char op;
+    int ret_len;
 
-    ND_TCHECK(*bp);
+    LCHECK2(length, 1);
+    op = GET_U_1(bp);
 
+    /* bp now points to the op, so these routines must skip it */
     switch(op) {
         case RESP_SIMPLE_STRING:  ret_len = resp_print_simple_string(ndo, bp, length);   break;
         case RESP_INTEGER:        ret_len = resp_print_integer(ndo, bp, length);         break;
@@ -193,132 +256,123 @@ resp_parse(netdissect_options *ndo, register const u_char *bp, int length)
         default:                  ret_len = resp_print_inline(ndo, bp, length);          break;
     }
 
-    TEST_RET_LEN(ret_len);
+    /*
+     * This gives up with a "truncated" indicator for all errors,
+     * including invalid packet errors; that's what we want, as
+     * we have to give up on further parsing in that case.
+     */
+    TEST_RET_LEN(ret_len) // without a semicolon
 
 trunc:
     return (-1);
 }
 
 static int
-resp_print_simple_string(netdissect_options *ndo, register const u_char *bp, int length) {
+resp_print_simple_string(netdissect_options *ndo, const u_char *bp, int length) {
     return resp_print_string_error_integer(ndo, bp, length);
 }
 
 static int
-resp_print_integer(netdissect_options *ndo, register const u_char *bp, int length) {
+resp_print_integer(netdissect_options *ndo, const u_char *bp, int length) {
     return resp_print_string_error_integer(ndo, bp, length);
 }
 
 static int
-resp_print_error(netdissect_options *ndo, register const u_char *bp, int length) {
+resp_print_error(netdissect_options *ndo, const u_char *bp, int length) {
     return resp_print_string_error_integer(ndo, bp, length);
 }
 
 static int
-resp_print_string_error_integer(netdissect_options *ndo, register const u_char *bp, int length) {
-    int length_cur = length, len, ret_len = 0;
-    const u_char *bp_ptr = bp;
+resp_print_string_error_integer(netdissect_options *ndo, const u_char *bp, int length) {
+    int length_cur = length, len, ret_len;
+    const u_char *bp_ptr;
+
+    /* bp points to the op; skip it */
+    SKIP_OPCODE(bp, length_cur);
+    bp_ptr = bp;
 
     /*
-     * MOVE_FORWARD moves past the string that follows the (+-;) opcodes
+     * bp now prints past the (+-;) opcode, so it's pointing to the first
+     * character of the string (which could be numeric).
      * +OK\r\n
      * -ERR ...\r\n
      * :02912309\r\n
+     *
+     * Find the \r\n with FIND_CRLF().
      */
-    MOVE_FORWARD(bp_ptr, length_cur);
-    len = (bp_ptr - bp);
-    ND_TCHECK2(*bp, len);
-    RESP_PRINT_SEGMENT(ndo, bp+1, len-1);
-    ret_len = len /*<1byte>+<string>*/ + 2 /*<CRLF>*/;
+    FIND_CRLF(bp_ptr, length_cur);
 
-    TEST_RET_LEN(ret_len);
+    /*
+     * bp_ptr points to the \r\n, so bp_ptr - bp is the length of text
+     * preceding the \r\n.  That includes the opcode, so don't print
+     * that.
+     */
+    len = ND_BYTES_BETWEEN(bp, bp_ptr);
+    RESP_PRINT_SEGMENT(ndo, bp, len);
+    ret_len = 1 /*<opcode>*/ + len /*<string>*/ + 2 /*<CRLF>*/;
+
+    TEST_RET_LEN(ret_len) // without a semicolon
 
 trunc:
     return (-1);
 }
 
 static int
-resp_print_bulk_string(netdissect_options *ndo, register const u_char *bp, int length) {
+resp_print_bulk_string(netdissect_options *ndo, const u_char *bp, int length) {
     int length_cur = length, string_len;
-    long strtol_ret;
-    char *p;
-
-    ND_TCHECK(*bp);
-
-    /* opcode: '$' */
-    INC1(bp, length_cur);
-    ND_TCHECK(*bp);
-
-    /* <length> */
-    errno = 0;
-    strtol_ret = strtol((const char *)bp, &p, 10);
-    if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
-        strtol_ret > INT_MAX)
-        string_len = -2; /* invalid */
-    else
-        string_len = (int)strtol_ret;
-
-    /* move to \r\n */
-    MOVE_FORWARD(bp, length_cur);
-
-    /* \r\n */
-    INC2(bp, length_cur);
-
-    if (string_len > 0) {
-        /* Byte string of length string_len */
-        ND_TCHECK2(*bp, string_len);
-        RESP_PRINT_SEGMENT(ndo, bp, string_len);
+
+    /* bp points to the op; skip it */
+    SKIP_OPCODE(bp, length_cur);
+
+    /* <length>\r\n */
+    GET_LENGTH(ndo, length_cur, bp, string_len);
+
+    if (string_len >= 0) {
+        /* Byte string of length string_len, starting at bp */
+        if (string_len == 0)
+            resp_print_empty(ndo);
+        else {
+            LCHECK2(length_cur, string_len);
+            ND_TCHECK_LEN(bp, string_len);
+            RESP_PRINT_SEGMENT(ndo, bp, string_len);
+            bp += string_len;
+            length_cur -= string_len;
+        }
+
+        /*
+         * Find the \r\n at the end of the string and skip past it.
+         * XXX - report an error if the \r\n isn't immediately after
+         * the item?
+         */
+        FIND_CRLF(bp, length_cur);
+        CONSUME_CRLF(bp, length_cur);
     } else {
+        /* null, truncated, or invalid for some reason */
         switch(string_len) {
-            case 0: resp_print_empty(ndo); break;
-            case (-1): {
-                /* This is the NULL response. It follows a different pattern: $-1\r\n */
-                resp_print_null(ndo);
-                TEST_RET_LEN(length - length_cur);
-                /* returned ret_len or jumped to trunc */
-            }
-            default: resp_print_invalid(ndo); break;
+            case (-1):  resp_print_null(ndo);             break;
+            case (-2):  goto trunc;
+            case (-3):  resp_print_length_too_large(ndo); break;
+            case (-4):  resp_print_length_negative(ndo);  break;
+            default:    resp_print_invalid(ndo);          break;
         }
     }
 
-    /* <string> */
-    INCBY(bp, string_len, length_cur);
-
-    /* \r\n */
-    INC2(bp, length_cur);
-
-    TEST_RET_LEN(length - length_cur);
+    return (length - length_cur);
 
 trunc:
     return (-1);
 }
 
 static int
-resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int length) {
-    int length_cur = length, array_len, i, ret_len = 0;
-    long strtol_ret;
-    char *p;
-
-    ND_TCHECK(*bp);
-
-    /* opcode: '*' */
-    INC1(bp, length_cur);
-    ND_TCHECK(*bp);
+resp_print_bulk_array(netdissect_options *ndo, const u_char *bp, int length) {
+    u_int length_cur = length;
+    int array_len, i, ret_len;
 
-    /* <array_length> */
-    errno = 0;
-    strtol_ret = strtol((const char *)bp, &p, 10);
-    if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
-        strtol_ret > INT_MAX)
-        array_len = -2; /* invalid */
-    else
-        array_len = (int)strtol_ret;
+    /* bp points to the op; skip it */
+    SKIP_OPCODE(bp, length_cur);
 
-    /* move to \r\n */
-    MOVE_FORWARD(bp, length_cur);
-
-    /* \r\n */
-    INC2(bp, length_cur);
+    /* <array_length>\r\n */
+    GET_LENGTH(ndo, length_cur, bp, array_len);
 
     if (array_len > 0) {
         /* non empty array */
@@ -329,47 +383,153 @@ resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int le
 
             bp += ret_len;
             length_cur -= ret_len;
-
-            TEST_RET_LEN_NORETURN(length - length_cur);
         }
     } else {
-        /* empty, null, or invalid */
+        /* empty, null, truncated, or invalid */
         switch(array_len) {
-            case 0:     resp_print_empty(ndo);   break;
-            case (-1):  resp_print_null(ndo);    break;
-            default:    resp_print_invalid(ndo); break;
+            case 0:     resp_print_empty(ndo);            break;
+            case (-1):  resp_print_null(ndo);             break;
+            case (-2):  goto trunc;
+            case (-3):  resp_print_length_too_large(ndo); break;
+            case (-4):  resp_print_length_negative(ndo);  break;
+            default:    resp_print_invalid(ndo);          break;
         }
     }
 
-    TEST_RET_LEN(length - length_cur);
+    return (length - length_cur);
 
 trunc:
     return (-1);
 }
 
 static int
-resp_print_inline(netdissect_options *ndo, register const u_char *bp, int length) {
-    int length_cur = length, len;
+resp_print_inline(netdissect_options *ndo, const u_char *bp, int length) {
+    int length_cur = length;
+    int len;
     const u_char *bp_ptr;
 
     /*
      * Inline commands are simply 'strings' followed by \r or \n or both.
-     * Redis will do it's best to split/parse these strings.
+     * Redis will do its best to split/parse these strings.
      * This feature of redis is implemented to support the ability of
      * command parsing from telnet/nc sessions etc.
      *
      * <string><\r||\n||\r\n...>
      */
+
+    /*
+     * Skip forward past any leading \r, \n, or \r\n.
+     */
     CONSUME_CR_OR_LF(bp, length_cur);
     bp_ptr = bp;
-    MOVE_FORWARD_CR_OR_LF(bp_ptr, length_cur);
-    len = (bp_ptr - bp);
-    ND_TCHECK2(*bp, len);
+
+    /*
+     * Scan forward looking for \r or \n.
+     */
+    FIND_CR_OR_LF(bp_ptr, length_cur);
+
+    /*
+     * Found it; bp_ptr points to the \r or \n, so bp_ptr - bp is the
+     * Length of the line text that precedes it.  Print it.
+     */
+    len = ND_BYTES_BETWEEN(bp, bp_ptr);
     RESP_PRINT_SEGMENT(ndo, bp, len);
+
+    /*
+     * Skip forward past the \r, \n, or \r\n.
+     */
     CONSUME_CR_OR_LF(bp_ptr, length_cur);
 
-    TEST_RET_LEN(length - length_cur);
+    /*
+     * Return the number of bytes we processed.
+     */
+    return (length - length_cur);
 
 trunc:
     return (-1);
 }
+
+static int
+resp_get_length(netdissect_options *ndo, const u_char *bp, int len, const u_char **endp)
+{
+    int result;
+    u_char c;
+    int saw_digit;
+    int neg;
+    int too_large;
+
+    if (len == 0)
+        goto trunc;
+    too_large = 0;
+    neg = 0;
+    if (GET_U_1(bp) == '-') {
+        neg = 1;
+        bp++;
+        len--;
+    }
+    result = 0;
+    saw_digit = 0;
+
+    for (;;) {
+        if (len == 0)
+            goto trunc;
+        c = GET_U_1(bp);
+        if (!(c >= '0' && c <= '9')) {
+            if (!saw_digit) {
+                bp++;
+                goto invalid;
+            }
+            break;
+        }
+        c -= '0';
+        if (result > (INT_MAX / 10)) {
+            /* This will overflow an int when we multiply it by 10. */
+            too_large = 1;
+        } else {
+            result *= 10;
+            if (result == ((INT_MAX / 10) * 10) && c > (INT_MAX % 10)) {
+                /* This will overflow an int when we add c */
+                too_large = 1;
+            } else
+                result += c;
+        }
+        bp++;
+        len--;
+        saw_digit = 1;
+    }
+
+    /*
+     * OK, we found a non-digit character.  It should be a \r, followed
+     * by a \n.
+     */
+    if (GET_U_1(bp) != '\r') {
+        bp++;
+        goto invalid;
+    }
+    bp++;
+    len--;
+    if (len == 0)
+        goto trunc;
+    if (GET_U_1(bp) != '\n') {
+        bp++;
+        goto invalid;
+    }
+    bp++;
+    len--;
+    *endp = bp;
+    if (neg) {
+        /* -1 means "null", anything else is invalid */
+        if (too_large || result != 1)
+            return (-4);
+        result = -1;
+    }
+    return (too_large ? -3 : result);
+
+trunc:
+    *endp = bp;
+    return (-2);
+
+invalid:
+    *endp = bp;
+    return (-5);
+}