Security fixes from redis: Avoid integer overflows in SETRANGE and SORT, Fix range issues in ZRANDMEMBER and HRANDFIELD (#547)
* Avoid integer overflows in SETRANGE and SORT (CVE-2022-35977) (#11720) 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. * Fix range issues in ZRANDMEMBER and HRANDFIELD (CVE-2023-22458) (#11674) missing range check in ZRANDMEMBER and HRANDIFLD leading to panic due to protocol limitations * use std::min/max * add assert_not_equal Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
parent
b50f0cc21f
commit
ad0be5666c
10
src/sort.cpp
10
src/sort.cpp
@ -194,8 +194,8 @@ void sortCommand(client *c) {
|
|||||||
list *operations;
|
list *operations;
|
||||||
unsigned int outputlen = 0;
|
unsigned int outputlen = 0;
|
||||||
int desc = 0, alpha = 0;
|
int desc = 0, alpha = 0;
|
||||||
long limit_start = 0, limit_count = -1, start, end;
|
long limit_start = 0, limit_count = -1, start, end, vectorlen;
|
||||||
int j, dontsort = 0, vectorlen;
|
int j, dontsort = 0;
|
||||||
int getop = 0; /* GET operation counter */
|
int getop = 0; /* GET operation counter */
|
||||||
int int_conversion_error = 0;
|
int int_conversion_error = 0;
|
||||||
int syntax_error = 0;
|
int syntax_error = 0;
|
||||||
@ -321,8 +321,10 @@ void sortCommand(client *c) {
|
|||||||
default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
|
default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Perform LIMIT start,count sanity checking. */
|
/* Perform LIMIT start,count sanity checking.
|
||||||
start = (limit_start < 0) ? 0 : limit_start;
|
* And avoid integer overflow by limiting inputs to object sizes. */
|
||||||
|
start = std::min(std::max(limit_start, (long)0), vectorlen);
|
||||||
|
limit_count = std::min(std::max(limit_count, (long)-1), vectorlen);
|
||||||
end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
|
end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
|
||||||
if (start >= vectorlen) {
|
if (start >= vectorlen) {
|
||||||
start = vectorlen-1;
|
start = vectorlen-1;
|
||||||
|
@ -1225,8 +1225,13 @@ void hrandfieldCommand(client *c) {
|
|||||||
if (c->argc > 4 || (c->argc == 4 && strcasecmp(szFromObj(c->argv[3]),"withvalues"))) {
|
if (c->argc > 4 || (c->argc == 4 && strcasecmp(szFromObj(c->argv[3]),"withvalues"))) {
|
||||||
addReplyErrorObject(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
} else if (c->argc == 4)
|
} else if (c->argc == 4) {
|
||||||
withvalues = 1;
|
withvalues = 1;
|
||||||
|
if (l < LONG_MIN/2 || l > LONG_MAX/2) {
|
||||||
|
addReplyError(c,"value is out of range");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
hrandfieldWithCountCommand(c, l, withvalues);
|
hrandfieldWithCountCommand(c, l, withvalues);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,14 @@ int getGenericCommand(client *c);
|
|||||||
* String Commands
|
* String Commands
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
static int checkStringLength(client *c, long long size) {
|
static int checkStringLength(client *c, long long size, long long append) {
|
||||||
if (!(c->flags & CLIENT_MASTER) && size > g_pserver->proto_max_bulk_len) {
|
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 > g_pserver->proto_max_bulk_len || total < size || total < append) {
|
||||||
addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");
|
addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
@ -445,7 +451,7 @@ void setrangeCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Return when the resulting string exceeds allowed size */
|
/* 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;
|
return;
|
||||||
|
|
||||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
|
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
|
||||||
@ -465,7 +471,7 @@ void setrangeCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Return when the resulting string exceeds allowed size */
|
/* 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;
|
return;
|
||||||
|
|
||||||
/* Create a copy when the object is shared or encoded. */
|
/* Create a copy when the object is shared or encoded. */
|
||||||
@ -685,8 +691,7 @@ void appendCommand(client *c) {
|
|||||||
|
|
||||||
/* "append" is an argument, so always an sds */
|
/* "append" is an argument, so always an sds */
|
||||||
append = c->argv[2];
|
append = c->argv[2];
|
||||||
totlen = stringObjectLen(o)+sdslen((sds)ptrFromObj(append));
|
if (checkStringLength(c,stringObjectLen(o),sdslen((sds)ptrFromObj(append))) != C_OK)
|
||||||
if (checkStringLength(c,totlen) != C_OK)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Append the value */
|
/* Append the value */
|
||||||
|
@ -4214,8 +4214,13 @@ void zrandmemberCommand(client *c) {
|
|||||||
if (c->argc > 4 || (c->argc == 4 && strcasecmp(szFromObj(c->argv[3]),"withscores"))) {
|
if (c->argc > 4 || (c->argc == 4 && strcasecmp(szFromObj(c->argv[3]),"withscores"))) {
|
||||||
addReplyErrorObject(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
} else if (c->argc == 4)
|
} else if (c->argc == 4) {
|
||||||
withscores = 1;
|
withscores = 1;
|
||||||
|
if (l < LONG_MIN/2 || l > LONG_MAX/2) {
|
||||||
|
addReplyError(c,"value is out of range");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
zrandmemberWithCountCommand(c, l, withscores);
|
zrandmemberWithCountCommand(c, l, withscores);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,12 @@ proc assert_failed {expected_err detail} {
|
|||||||
error "assertion:$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 ""}} {
|
proc assert_equal {value expected {detail ""}} {
|
||||||
if {$expected ne $value} {
|
if {$expected ne $value} {
|
||||||
assert_failed "Expected '$value' to be equal to '$expected'" $detail
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,11 @@ start_server {tags {"hash"}} {
|
|||||||
r hrandfield myhash 0
|
r hrandfield myhash 0
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
|
test "HRANDFIELD count overflow" {
|
||||||
|
r hmset myhash a 1
|
||||||
|
assert_error {*value is out of range*} {r hrandfield myhash -9223372036854770000 withvalues}
|
||||||
|
} {}
|
||||||
|
|
||||||
test "HRANDFIELD with <count> against non existing key" {
|
test "HRANDFIELD with <count> against non existing key" {
|
||||||
r hrandfield nonexisting_key 100
|
r hrandfield nonexisting_key 100
|
||||||
} {}
|
} {}
|
||||||
|
@ -574,4 +574,14 @@ start_server {tags {"string"}} {
|
|||||||
test {LCS indexes with match len and minimum match len} {
|
test {LCS indexes with match len and minimum match len} {
|
||||||
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN MINMATCHLEN 5] matches
|
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN MINMATCHLEN 5] matches
|
||||||
} {{{1 222} {13 234} 222}}
|
} {{{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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1714,6 +1714,11 @@ start_server {tags {"zset"}} {
|
|||||||
r zrandmember nonexisting_key 100
|
r zrandmember nonexisting_key 100
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
|
test "ZRANDMEMBER count overflow" {
|
||||||
|
r zadd myzset 0 a
|
||||||
|
assert_error {*value is out of range*} {r zrandmember myzset -9223372036854770000 withscores}
|
||||||
|
} {}
|
||||||
|
|
||||||
# Make sure we can distinguish between an empty array and a null response
|
# Make sure we can distinguish between an empty array and a null response
|
||||||
r readraw 1
|
r readraw 1
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user