https://github.com/redis/redis/blob/90e9fc387c81d48d3d82ea7fcd2d3b6d896bf923/src/quicklist.c#L502C1-L503C82
A certain call to function quicklistReplaceEntry can make node->sz greater than 1GB
Comment From: sundb
the comment means that node->sz was already checked by if (unlikely(QL_NODE_IS_PLAIN(node) || isLargeElement(sz))).
Comment From: imchuncai
the comment means that
node->szwas already checked byif (unlikely(QL_NODE_IS_PLAIN(node) || isLargeElement(sz))).
The following code will create a packed node with (node.sz >= 1GB)
TEST("pack limit")
{
quicklist *ql = quicklistNew(-5, 0);
quicklistPushHead(ql, "0", 1);
quicklistPushHead(ql, "1", 1);
/* replace with 0.5GB entry twice*/
size_t sz = (1 << 29);
void *s = zmalloc(sz);
quicklistReplaceAtIndex(ql, 0, s, sz);
quicklistReplaceAtIndex(ql, 1, s, sz);
assert(ql->len == 1);
quicklistNode *node = ql->head;
assert(!QL_NODE_IS_PLAIN(node));
assert(node->sz >= (1 << 30));
zfree(s);
quicklistRelease(ql);
}
Comment From: sundb
@imchuncai Yeap, you are right. In this scenario, a single node can reach a maximum of 2GB, even so, this code is still safe. Do You want to submit a PR to fix this comment?
Comment From: imchuncai
@sundb yes, I will submit.
Comment From: imchuncai
https://github.com/redis/redis/blob/90e9fc387c81d48d3d82ea7fcd2d3b6d896bf923/src/quicklist.c#L68C1-L73C31 @sundb So according to the following code, this comment is wrong either?
TEST("pack limit B")
{
/* ql listpack is limited by count */
quicklist *ql = quicklistNew(2, 0);
quicklistPushHead(ql, "0", 1);
quicklistPushHead(ql, "1", 1);
/* replace with 0.5GB entry*/
size_t sz = (1 << 29);
void *s = zmalloc(sz);
quicklistReplaceAtIndex(ql, 0, s, sz);
assert(ql->len == 1);
quicklistNode *node = ql->head;
/* multi-element listpack */
assert(!QL_NODE_IS_PLAIN(node));
assert(node->count > 1);
/* against SIZE_SAFETY_LIMIT */
assert(node->sz > SIZE_SAFETY_LIMIT);
zfree(s);
quicklistRelease(ql);
}
TEST("pack limit C")
{
/* ql listpack is limited by count */
quicklist *ql = quicklistNew(2, 0);
quicklistPushHead(ql, "0", 1);
quicklistPushHead(ql, "1", 1);
/* insert 0.5GB entry*/
size_t sz = (1 << 29);
void *s = zmalloc(sz);
quicklistEntry entry;
struct quicklistIter *iter =
quicklistGetIteratorEntryAtIdx(ql, 0, &entry);
quicklistInsertAfter(iter, &entry, s, sz);
quicklistReleaseIterator(iter);
assert(ql->len == 2);
quicklistNode *node = ql->head->next;
/* multi-element listpack */
assert(!QL_NODE_IS_PLAIN(node));
assert(node->count > 1);
/* against SIZE_SAFETY_LIMIT */
assert(node->sz > SIZE_SAFETY_LIMIT);
zfree(s);
quicklistRelease(ql);
}
Shall we considering about the function (quicklistReplaceEntry), (quicklistInsertBefore) and (quicklistInsertAfter) are not working as expected?
Comment From: sundb
@imchuncai i think that we might be able to modify quicklistReplaceEntry to avoid these confusions.
like
if (likely(!QL_NODE_IS_PLAIN(entry->node) &&
(sz <= entry->sz ||
(sz > entry->sz && _quicklistNodeAllowInsert(entry->node, quicklist->fill, sz - entry->sz)))))