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