1 /* 2 * Copyright (c) 2015 The TCPDUMP project 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 17 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 18 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 24 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 * POSSIBILITY OF SUCH DAMAGE. 26 * 27 * Initial contribution by Andrew Darqui (andrew.darqui@gmail.com). 28 */ 29 30 /* \summary: REdis Serialization Protocol (RESP) printer */ 31 32 #include <config.h> 33 34 #include "netdissect-stdinc.h" 35 #include "netdissect.h" 36 #include <limits.h> 37 38 #include "extract.h" 39 40 41 /* 42 * For information regarding RESP, see: https://redis.io/topics/protocol 43 */ 44 45 #define RESP_SIMPLE_STRING '+' 46 #define RESP_ERROR '-' 47 #define RESP_INTEGER ':' 48 #define RESP_BULK_STRING '$' 49 #define RESP_ARRAY '*' 50 51 #define resp_print_empty(ndo) ND_PRINT(" empty") 52 #define resp_print_null(ndo) ND_PRINT(" null") 53 #define resp_print_length_too_large(ndo) ND_PRINT(" length too large") 54 #define resp_print_length_negative(ndo) ND_PRINT(" length negative and not -1") 55 #define resp_print_invalid(ndo) ND_PRINT(" invalid") 56 57 static int resp_parse(netdissect_options *, const u_char *, int); 58 static int resp_print_string_error_integer(netdissect_options *, const u_char *, int); 59 static int resp_print_simple_string(netdissect_options *, const u_char *, int); 60 static int resp_print_integer(netdissect_options *, const u_char *, int); 61 static int resp_print_error(netdissect_options *, const u_char *, int); 62 static int resp_print_bulk_string(netdissect_options *, const u_char *, int); 63 static int resp_print_bulk_array(netdissect_options *, const u_char *, int); 64 static int resp_print_inline(netdissect_options *, const u_char *, int); 65 static int resp_get_length(netdissect_options *, const u_char *, int, const u_char **); 66 67 #define LCHECK2(_tot_len, _len) \ 68 { \ 69 if (_tot_len < _len) \ 70 goto trunc; \ 71 } 72 73 #define LCHECK(_tot_len) LCHECK2(_tot_len, 1) 74 75 /* 76 * FIND_CRLF: 77 * Attempts to move our 'ptr' forward until a \r\n is found, 78 * while also making sure we don't exceed the buffer '_len' 79 * or go past the end of the captured data. 80 * If we exceed or go past the end of the captured data, 81 * jump to trunc. 82 */ 83 #define FIND_CRLF(_ptr, _len) \ 84 for (;;) { \ 85 LCHECK2(_len, 2); \ 86 ND_TCHECK_2(_ptr); \ 87 if (GET_U_1(_ptr) == '\r' && \ 88 GET_U_1(_ptr+1) == '\n') \ 89 break; \ 90 _ptr++; \ 91 _len--; \ 92 } 93 94 /* 95 * CONSUME_CRLF 96 * Consume a CRLF that we've just found. 97 */ 98 #define CONSUME_CRLF(_ptr, _len) \ 99 _ptr += 2; \ 100 _len -= 2; 101 102 /* 103 * FIND_CR_OR_LF 104 * Attempts to move our '_ptr' forward until a \r or \n is found, 105 * while also making sure we don't exceed the buffer '_len' 106 * or go past the end of the captured data. 107 * If we exceed or go past the end of the captured data, 108 * jump to trunc. 109 */ 110 #define FIND_CR_OR_LF(_ptr, _len) \ 111 for (;;) { \ 112 LCHECK(_len); \ 113 if (GET_U_1(_ptr) == '\r' || \ 114 GET_U_1(_ptr) == '\n') \ 115 break; \ 116 _ptr++; \ 117 _len--; \ 118 } 119 120 /* 121 * CONSUME_CR_OR_LF 122 * Consume all consecutive \r and \n bytes. 123 * If we exceed '_len' or go past the end of the captured data, 124 * jump to trunc. 125 */ 126 #define CONSUME_CR_OR_LF(_ptr, _len) \ 127 { \ 128 int _found_cr_or_lf = 0; \ 129 for (;;) { \ 130 /* \ 131 * Have we hit the end of data? \ 132 */ \ 133 if (_len == 0 || !ND_TTEST_1(_ptr)) {\ 134 /* \ 135 * Yes. Have we seen a \r \ 136 * or \n? \ 137 */ \ 138 if (_found_cr_or_lf) { \ 139 /* \ 140 * Yes. Just stop. \ 141 */ \ 142 break; \ 143 } \ 144 /* \ 145 * No. We ran out of packet. \ 146 */ \ 147 goto trunc; \ 148 } \ 149 if (GET_U_1(_ptr) != '\r' && \ 150 GET_U_1(_ptr) != '\n') \ 151 break; \ 152 _found_cr_or_lf = 1; \ 153 _ptr++; \ 154 _len--; \ 155 } \ 156 } 157 158 /* 159 * SKIP_OPCODE 160 * Skip over the opcode character. 161 * The opcode has already been fetched, so we know it's there, and don't 162 * need to do any checks. 163 */ 164 #define SKIP_OPCODE(_ptr, _tot_len) \ 165 _ptr++; \ 166 _tot_len--; 167 168 /* 169 * GET_LENGTH 170 * Get a bulk string or array length. 171 */ 172 #define GET_LENGTH(_ndo, _tot_len, _ptr, _len) \ 173 { \ 174 const u_char *_endp; \ 175 _len = resp_get_length(_ndo, _ptr, _tot_len, &_endp); \ 176 _tot_len -= (_endp - _ptr); \ 177 _ptr = _endp; \ 178 } 179 180 /* 181 * TEST_RET_LEN 182 * If ret_len is < 0, jump to the trunc tag which returns (-1) 183 * and 'bubbles up' to printing tstr. Otherwise, return ret_len. 184 */ 185 #define TEST_RET_LEN(rl) \ 186 if (rl < 0) { goto trunc; } else { return rl; } 187 188 /* 189 * TEST_RET_LEN_NORETURN 190 * If ret_len is < 0, jump to the trunc tag which returns (-1) 191 * and 'bubbles up' to printing tstr. Otherwise, continue onward. 192 */ 193 #define TEST_RET_LEN_NORETURN(rl) \ 194 if (rl < 0) { goto trunc; } 195 196 /* 197 * RESP_PRINT_SEGMENT 198 * Prints a segment in the form of: ' "<stuff>"\n" 199 * Assumes the data has already been verified as present. 200 */ 201 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \ 202 ND_PRINT(" \""); \ 203 if (nd_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \ 204 goto trunc; \ 205 fn_print_char(_ndo, '"'); 206 207 void 208 resp_print(netdissect_options *ndo, const u_char *bp, u_int length) 209 { 210 int ret_len = 0; 211 212 ndo->ndo_protocol = "resp"; 213 214 ND_PRINT(": RESP"); 215 while (length > 0) { 216 /* 217 * This block supports redis pipelining. 218 * For example, multiple operations can be pipelined within the same string: 219 * "*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n" 220 * or 221 * "PING\r\nPING\r\nPING\r\n" 222 * In order to handle this case, we must try and parse 'bp' until 223 * 'length' bytes have been processed or we reach a trunc condition. 224 */ 225 ret_len = resp_parse(ndo, bp, length); 226 TEST_RET_LEN_NORETURN(ret_len); 227 bp += ret_len; 228 length -= ret_len; 229 } 230 231 return; 232 233 trunc: 234 nd_print_trunc(ndo); 235 } 236 237 static int 238 resp_parse(netdissect_options *ndo, const u_char *bp, int length) 239 { 240 u_char op; 241 int ret_len; 242 243 LCHECK2(length, 1); 244 op = GET_U_1(bp); 245 246 /* bp now points to the op, so these routines must skip it */ 247 switch(op) { 248 case RESP_SIMPLE_STRING: ret_len = resp_print_simple_string(ndo, bp, length); break; 249 case RESP_INTEGER: ret_len = resp_print_integer(ndo, bp, length); break; 250 case RESP_ERROR: ret_len = resp_print_error(ndo, bp, length); break; 251 case RESP_BULK_STRING: ret_len = resp_print_bulk_string(ndo, bp, length); break; 252 case RESP_ARRAY: ret_len = resp_print_bulk_array(ndo, bp, length); break; 253 default: ret_len = resp_print_inline(ndo, bp, length); break; 254 } 255 256 /* 257 * This gives up with a "truncated" indicator for all errors, 258 * including invalid packet errors; that's what we want, as 259 * we have to give up on further parsing in that case. 260 */ 261 TEST_RET_LEN(ret_len); 262 263 trunc: 264 return (-1); 265 } 266 267 static int 268 resp_print_simple_string(netdissect_options *ndo, const u_char *bp, int length) { 269 return resp_print_string_error_integer(ndo, bp, length); 270 } 271 272 static int 273 resp_print_integer(netdissect_options *ndo, const u_char *bp, int length) { 274 return resp_print_string_error_integer(ndo, bp, length); 275 } 276 277 static int 278 resp_print_error(netdissect_options *ndo, const u_char *bp, int length) { 279 return resp_print_string_error_integer(ndo, bp, length); 280 } 281 282 static int 283 resp_print_string_error_integer(netdissect_options *ndo, const u_char *bp, int length) { 284 int length_cur = length, len, ret_len; 285 const u_char *bp_ptr; 286 287 /* bp points to the op; skip it */ 288 SKIP_OPCODE(bp, length_cur); 289 bp_ptr = bp; 290 291 /* 292 * bp now prints past the (+-;) opcode, so it's pointing to the first 293 * character of the string (which could be numeric). 294 * +OK\r\n 295 * -ERR ...\r\n 296 * :02912309\r\n 297 * 298 * Find the \r\n with FIND_CRLF(). 299 */ 300 FIND_CRLF(bp_ptr, length_cur); 301 302 /* 303 * bp_ptr points to the \r\n, so bp_ptr - bp is the length of text 304 * preceding the \r\n. That includes the opcode, so don't print 305 * that. 306 */ 307 len = ND_BYTES_BETWEEN(bp, bp_ptr); 308 RESP_PRINT_SEGMENT(ndo, bp, len); 309 ret_len = 1 /*<opcode>*/ + len /*<string>*/ + 2 /*<CRLF>*/; 310 311 TEST_RET_LEN(ret_len); 312 313 trunc: 314 return (-1); 315 } 316 317 static int 318 resp_print_bulk_string(netdissect_options *ndo, const u_char *bp, int length) { 319 int length_cur = length, string_len; 320 321 /* bp points to the op; skip it */ 322 SKIP_OPCODE(bp, length_cur); 323 324 /* <length>\r\n */ 325 GET_LENGTH(ndo, length_cur, bp, string_len); 326 327 if (string_len >= 0) { 328 /* Byte string of length string_len, starting at bp */ 329 if (string_len == 0) 330 resp_print_empty(ndo); 331 else { 332 LCHECK2(length_cur, string_len); 333 ND_TCHECK_LEN(bp, string_len); 334 RESP_PRINT_SEGMENT(ndo, bp, string_len); 335 bp += string_len; 336 length_cur -= string_len; 337 } 338 339 /* 340 * Find the \r\n at the end of the string and skip past it. 341 * XXX - report an error if the \r\n isn't immediately after 342 * the item? 343 */ 344 FIND_CRLF(bp, length_cur); 345 CONSUME_CRLF(bp, length_cur); 346 } else { 347 /* null, truncated, or invalid for some reason */ 348 switch(string_len) { 349 case (-1): resp_print_null(ndo); break; 350 case (-2): goto trunc; 351 case (-3): resp_print_length_too_large(ndo); break; 352 case (-4): resp_print_length_negative(ndo); break; 353 default: resp_print_invalid(ndo); break; 354 } 355 } 356 357 return (length - length_cur); 358 359 trunc: 360 return (-1); 361 } 362 363 static int 364 resp_print_bulk_array(netdissect_options *ndo, const u_char *bp, int length) { 365 u_int length_cur = length; 366 int array_len, i, ret_len; 367 368 /* bp points to the op; skip it */ 369 SKIP_OPCODE(bp, length_cur); 370 371 /* <array_length>\r\n */ 372 GET_LENGTH(ndo, length_cur, bp, array_len); 373 374 if (array_len > 0) { 375 /* non empty array */ 376 for (i = 0; i < array_len; i++) { 377 ret_len = resp_parse(ndo, bp, length_cur); 378 379 TEST_RET_LEN_NORETURN(ret_len); 380 381 bp += ret_len; 382 length_cur -= ret_len; 383 } 384 } else { 385 /* empty, null, truncated, or invalid */ 386 switch(array_len) { 387 case 0: resp_print_empty(ndo); break; 388 case (-1): resp_print_null(ndo); break; 389 case (-2): goto trunc; 390 case (-3): resp_print_length_too_large(ndo); break; 391 case (-4): resp_print_length_negative(ndo); break; 392 default: resp_print_invalid(ndo); break; 393 } 394 } 395 396 return (length - length_cur); 397 398 trunc: 399 return (-1); 400 } 401 402 static int 403 resp_print_inline(netdissect_options *ndo, const u_char *bp, int length) { 404 int length_cur = length; 405 int len; 406 const u_char *bp_ptr; 407 408 /* 409 * Inline commands are simply 'strings' followed by \r or \n or both. 410 * Redis will do its best to split/parse these strings. 411 * This feature of redis is implemented to support the ability of 412 * command parsing from telnet/nc sessions etc. 413 * 414 * <string><\r||\n||\r\n...> 415 */ 416 417 /* 418 * Skip forward past any leading \r, \n, or \r\n. 419 */ 420 CONSUME_CR_OR_LF(bp, length_cur); 421 bp_ptr = bp; 422 423 /* 424 * Scan forward looking for \r or \n. 425 */ 426 FIND_CR_OR_LF(bp_ptr, length_cur); 427 428 /* 429 * Found it; bp_ptr points to the \r or \n, so bp_ptr - bp is the 430 * Length of the line text that precedes it. Print it. 431 */ 432 len = ND_BYTES_BETWEEN(bp, bp_ptr); 433 RESP_PRINT_SEGMENT(ndo, bp, len); 434 435 /* 436 * Skip forward past the \r, \n, or \r\n. 437 */ 438 CONSUME_CR_OR_LF(bp_ptr, length_cur); 439 440 /* 441 * Return the number of bytes we processed. 442 */ 443 return (length - length_cur); 444 445 trunc: 446 return (-1); 447 } 448 449 static int 450 resp_get_length(netdissect_options *ndo, const u_char *bp, int len, const u_char **endp) 451 { 452 int result; 453 u_char c; 454 int saw_digit; 455 int neg; 456 int too_large; 457 458 if (len == 0) 459 goto trunc; 460 too_large = 0; 461 neg = 0; 462 if (GET_U_1(bp) == '-') { 463 neg = 1; 464 bp++; 465 len--; 466 } 467 result = 0; 468 saw_digit = 0; 469 470 for (;;) { 471 if (len == 0) 472 goto trunc; 473 c = GET_U_1(bp); 474 if (!(c >= '0' && c <= '9')) { 475 if (!saw_digit) { 476 bp++; 477 goto invalid; 478 } 479 break; 480 } 481 c -= '0'; 482 if (result > (INT_MAX / 10)) { 483 /* This will overflow an int when we multiply it by 10. */ 484 too_large = 1; 485 } else { 486 result *= 10; 487 if (result == ((INT_MAX / 10) * 10) && c > (INT_MAX % 10)) { 488 /* This will overflow an int when we add c */ 489 too_large = 1; 490 } else 491 result += c; 492 } 493 bp++; 494 len--; 495 saw_digit = 1; 496 } 497 498 /* 499 * OK, we found a non-digit character. It should be a \r, followed 500 * by a \n. 501 */ 502 if (GET_U_1(bp) != '\r') { 503 bp++; 504 goto invalid; 505 } 506 bp++; 507 len--; 508 if (len == 0) 509 goto trunc; 510 if (GET_U_1(bp) != '\n') { 511 bp++; 512 goto invalid; 513 } 514 bp++; 515 len--; 516 *endp = bp; 517 if (neg) { 518 /* -1 means "null", anything else is invalid */ 519 if (too_large || result != 1) 520 return (-4); 521 result = -1; 522 } 523 return (too_large ? -3 : result); 524 525 trunc: 526 *endp = bp; 527 return (-2); 528 529 invalid: 530 *endp = bp; 531 return (-5); 532 } 533