Commit 54538998 authored by Oran Agra's avatar Oran Agra
Browse files

Avoid integer overflows in SETRANGE and SORT (CVE-2022-35977)

Authenticated users issuing specially crafted SETRANGE and SORT(_RO)
commands can trigger an integer overflow, resulting with Redis attempting
to allocate impossible amounts of memory and abort with an OOM panic.
parent 3148f3e8
......@@ -84,6 +84,12 @@ typedef long long ustime_t; /* microsecond time type. */
#include "endianconv.h"
#include "crc64.h"
/* min/max */
#undef min
#undef max
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
/* Error codes */
#define C_OK 0
#define C_ERR -1
......
......@@ -320,8 +320,10 @@ void sortCommand(client *c) {
default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
}
/* Perform LIMIT start,count sanity checking. */
start = (limit_start < 0) ? 0 : limit_start;
/* Perform LIMIT start,count sanity checking.
* And avoid integer overflow by limiting inputs to object sizes. */
start = min(max(limit_start, 0), vectorlen);
limit_count = min(max(limit_count, -1), vectorlen);
end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
if (start >= vectorlen) {
start = vectorlen-1;
......
......@@ -37,8 +37,14 @@ int getGenericCommand(client *c);
* String Commands
*----------------------------------------------------------------------------*/
static int checkStringLength(client *c, long long size) {
if (!(c->flags & CLIENT_MASTER) && size > server.proto_max_bulk_len) {
static int checkStringLength(client *c, long long size, long long append) {
if (c->flags & CLIENT_MASTER)
return C_OK;
/* 'uint64_t' cast is there just to prevent undefined behavior on overflow */
long long total = (uint64_t)size + append;
/* Test configured max-bulk-len represending a limit of the biggest string object,
* and also test for overflow. */
if (total > server.proto_max_bulk_len || total < size || total < append) {
addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");
return C_ERR;
}
......@@ -443,7 +449,7 @@ void setrangeCommand(client *c) {
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset+sdslen(value)) != C_OK)
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
return;
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
......@@ -463,7 +469,7 @@ void setrangeCommand(client *c) {
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset+sdslen(value)) != C_OK)
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
return;
/* Create a copy when the object is shared or encoded. */
......@@ -687,8 +693,7 @@ void appendCommand(client *c) {
/* "append" is an argument, so always an sds */
append = c->argv[2];
totlen = stringObjectLen(o)+sdslen(append->ptr);
if (checkStringLength(c,totlen) != C_OK)
if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK)
return;
/* Append the value */
......
......@@ -40,6 +40,12 @@ proc assert_failed {expected_err detail} {
error "assertion:$expected_err $detail"
}
proc assert_not_equal {value expected {detail ""}} {
if {!($expected ne $value)} {
assert_failed "Expected '$value' not equal to '$expected'" $detail
}
}
proc assert_equal {value expected {detail ""}} {
if {$expected ne $value} {
assert_failed "Expected '$value' to be equal to '$expected'" $detail
......
......@@ -315,4 +315,15 @@ start_server {
}
}
}
test {SETRANGE with huge offset} {
r lpush L 2 1 0
# expecting a different outcome on 32 and 64 bit systems
foreach value {9223372036854775807 2147483647} {
catch {[r sort_ro L by a limit 2 $value]} res
if {![string match "2" $res] && ![string match "*out of range*" $res]} {
assert_not_equal $res "expecting an error or 2"
}
}
}
}
......@@ -574,4 +574,14 @@ start_server {tags {"string"}} {
test {LCS indexes with match len and minimum match len} {
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN MINMATCHLEN 5] matches
} {{{1 222} {13 234} 222}}
test {SETRANGE with huge offset} {
foreach value {9223372036854775807 2147483647} {
catch {[r setrange K $value A]} res
# expecting a different error on 32 and 64 bit systems
if {![string match "*string exceeds maximum allowed size*" $res] && ![string match "*out of range*" $res]} {
assert_equal $res "expecting an error"
}
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment