Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -2996,6 +2996,9 @@ void handleParseError(client *c) {
} else if (flags & READ_FLAGS_ERROR_UNBALANCED_QUOTES) {
addReplyError(c, "Protocol error: unbalanced quotes in request");
setProtocolError("unbalanced quotes in inline request", c);
} else if (flags & READ_FLAGS_ERROR_INVALID_CRLF) {
addReplyError(c, "Protocol error: invalid CRLF in request");
setProtocolError("invalid CRLF in request", c);
} else if (flags & READ_FLAGS_ERROR_UNEXPECTED_INLINE_FROM_REPLICATED_CLIENT) {
if (getClientType(c) == CLIENT_TYPE_SLOT_IMPORT) {
serverLog(LL_WARNING, "WARNING: Receiving inline protocol from slot import, import stream corruption? Closing the "
Expand All @@ -3016,7 +3019,8 @@ int isParsingError(client *c) {
READ_FLAGS_ERROR_INVALID_MULTIBULK_LEN | READ_FLAGS_ERROR_UNAUTHENTICATED_MULTIBULK_LEN |
READ_FLAGS_ERROR_UNAUTHENTICATED_BULK_LEN | READ_FLAGS_ERROR_MBULK_INVALID_BULK_LEN |
READ_FLAGS_ERROR_BIG_BULK_COUNT | READ_FLAGS_ERROR_MBULK_UNEXPECTED_CHARACTER |
READ_FLAGS_ERROR_UNEXPECTED_INLINE_FROM_REPLICATED_CLIENT | READ_FLAGS_ERROR_UNBALANCED_QUOTES);
READ_FLAGS_ERROR_UNEXPECTED_INLINE_FROM_REPLICATED_CLIENT | READ_FLAGS_ERROR_UNBALANCED_QUOTES |
READ_FLAGS_ERROR_INVALID_CRLF);
}

/* This function is called after the query-buffer was parsed.
Expand Down Expand Up @@ -3493,6 +3497,11 @@ static int parseMultibulk(client *c,
/* Buffer should also contain \n */
if (newline - (c->querybuf + c->qb_pos) > (ssize_t)(sdslen(c->querybuf) - c->qb_pos - 2)) return 0;

/* Check that what follows \r is a real \n */
if (newline[1] != '\n') {
return READ_FLAGS_ERROR_INVALID_CRLF;
}

Comment on lines 3493 to 3504
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the first check is for a case when we read up to \r but the \n is not yet read into the buffer right? Maybe we should have made a similar check like we do for inline protocol and just search for the \n and not \r?
not sure if that would simplify things but it seems like it would make the check in line 3490 more accurate...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, maybe, there were some history i don't know about, this code is quite critical and i want to change it as little as possible to avoid any unexpected issues, that would be great if have some protocol fuzzer.

5af3020
bf9fd5f

/* We know for sure there is a whole line since newline != NULL,
* so go ahead and find out the multi bulk length. */
serverAssertWithInfo(c, NULL, c->querybuf[c->qb_pos] == '*');
Expand Down Expand Up @@ -3572,6 +3581,11 @@ static int parseMultibulk(client *c,
return READ_FLAGS_ERROR_MBULK_UNEXPECTED_CHARACTER;
}

/* Check that what follows \r is a real \n */
if (newline[1] != '\n') {
return READ_FLAGS_ERROR_INVALID_CRLF;
}

size_t bulklen_slen = newline - (c->querybuf + c->qb_pos + 1);
ok = string2ll(c->querybuf + c->qb_pos + 1, bulklen_slen, &ll);
if (!ok || ll < 0 || (!(is_replicated) && ll > server.proto_max_bulk_len)) {
Expand Down Expand Up @@ -3627,6 +3641,11 @@ static int parseMultibulk(client *c,
*argv = zrealloc(*argv, sizeof(robj *) * (*argv_len));
}

/* Check that what follows argv is a real \r\n */
if (c->querybuf[c->qb_pos + c->bulklen] != '\r' || c->querybuf[c->qb_pos + c->bulklen + 1] != '\n') {
return READ_FLAGS_ERROR_INVALID_CRLF;
}

/* Optimization: if a non-replicated client's buffer contains JUST our bulk element
* instead of creating a new object by *copying* the sds we
* just use the current sds string. */
Expand Down
1 change: 1 addition & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -2783,6 +2783,7 @@ void dictVanillaFree(void *val);
#define READ_FLAGS_NO_KEYS (1 << 19)
#define READ_FLAGS_CROSSSLOT (1 << 20)
#define READ_FLAGS_PREFETCHED (1 << 21)
#define READ_FLAGS_ERROR_INVALID_CRLF (1 << 22)

/* Write flags for various write errors and states */
#define WRITE_FLAGS_WRITE_ERROR (1 << 0)
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/protocol.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,62 @@ start_server {tags {"protocol network"}} {
set _ ""
} {} {needs:debug resp3}

test "binbin test" {
# Command) SET key value
# RESP) *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
# We need to strictly check these \r\n characters.
set proto "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
reconnect; r write $proto; r flush; assert_equal "OK" [r read]

# Check if multibulklen `*3\r\n` is followed by `\r\n`
set proto1 "*3x\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
set proto2 "*3\rx\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
set proto3 "*3xx\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
r write $proto1; r flush; assert_error "*invalid multibulk length*" {r read}; reconnect
r write $proto2; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto3; r flush; assert_error "*invalid multibulk length*" {r read}; reconnect

# Check if bulklen `$3\r\n` is followed by `\r\n`
set proto1 "*3\r\n\$3x\nSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
set proto2 "*3\r\n\$3\rxSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
set proto3 "*3\r\n\$3xxSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
r write $proto1; r flush; assert_error "*invalid bulk length*" {r read}; reconnect
r write $proto2; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto3; r flush; assert_error "*invalid bulk length*" {r read}; reconnect

# Check if `SET\r\n` is followed by `\r\n`
set proto1 "*3\r\n\$3\r\nSET\rx\$3\r\nkey\r\n\$5\r\nvalue\r\n"
set proto2 "*3\r\n\$3\r\nSETx\n\$3\r\nkey\r\n\$5\r\nvalue\r\n"
set proto3 "*3\r\n\$3\r\nSETxx\$3\r\nkey\r\n\$5\r\nvalue\r\n"
r write $proto1; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto2; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto3; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect

# Check if `key\r\n` is followed by `\r\n`
set proto1 "*3\r\n\$3\r\nSET\r\n\$3\r\nkeyx\n\$5\r\nvalue\r\n"
set proto2 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\rx\$5\r\nvalue\r\n"
set proto3 "*3\r\n\$3\r\nSET\r\n\$3\r\nkeyxx\$5\r\nvalue\r\n"
r write $proto1; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto2; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto3; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect

# Check if bulklen `$5\r\n` is followed by `\r\n`
set proto1 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5x\nvalue\r\n"
set proto2 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\rxvalue\r\n"
set proto3 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5xxvalue\r\n"
r write $proto1; r flush; assert_error "*invalid bulk length*" {r read}; reconnect
r write $proto2; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto3; r flush; assert_error "*invalid bulk length*" {r read}; reconnect

# Check if `value\r\n` is followed by `\r\n`
set proto1 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvaluex\n"
set proto2 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvalue\rx"
set proto3 "*3\r\n\$3\r\nSET\r\n\$3\r\nkey\r\n\$5\r\nvaluexx"
r write $proto1; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto2; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
r write $proto3; r flush; assert_error "*invalid CRLF in request*" {r read}; reconnect
}

test {RESP3 attributes readraw} {
r hello 3
r readraw 1
Expand Down
Loading