mirror of
https://github.com/redis/redis.git
synced 2026-06-09 00:33:08 -04:00
Fix use-after-free when fullsync happens while replica is running a timed out script (CVE-2026-23631)
Fullsync triggers emptyData and scriptingReset which free the scripting/function engine. If a timed out script is still running on the replica, this causes a use-after-free. Delay fullsync processing in readSyncBulkPayload until the script finishes.
This commit is contained in:
parent
8cdf9391da
commit
837ca7f8f4
2 changed files with 82 additions and 0 deletions
|
|
@ -2251,6 +2251,11 @@ void replicationAttachToNewMaster(void) {
|
|||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(connection *conn) {
|
||||
/* During full sync, the functions engine is freed right before loading
|
||||
* the RDB. To avoid this happening while a function is still running,
|
||||
* delay full sync processing until it finishes. */
|
||||
if (isInsideYieldingLongCommand()) return;
|
||||
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
ssize_t nread, readlen, nwritten;
|
||||
int use_diskless_load = useDisklessLoad();
|
||||
|
|
|
|||
|
|
@ -1878,3 +1878,80 @@ start_server {tags {"repl external:skip"}} {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Fullsync should not free the functions lib ctx while the replica has
|
||||
# a timed out function that is still running.
|
||||
foreach type {script function} {
|
||||
start_server {tags {"repl external:skip"}} {
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set replica [srv 0 client]
|
||||
|
||||
test "Fullsync should not free scripting engine on a replica while a $type is running" {
|
||||
$master config set repl-diskless-sync yes
|
||||
$master config set repl-diskless-sync-delay 0
|
||||
# Set small client output buffer limit to trigger fullsync quickly
|
||||
$master config set client-output-buffer-limit "replica 1k 1k 0"
|
||||
$replica config set repl-diskless-load yes
|
||||
$replica config set busy-reply-threshold 1 ;# script timeout in 1 ms
|
||||
|
||||
# Load function
|
||||
if {$type eq "function"} {
|
||||
$master function load replace {#!lua name=blocklib
|
||||
redis.register_function{
|
||||
function_name='blockfunc',
|
||||
callback=function() while true do end end,
|
||||
flags={'no-writes'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Start replication
|
||||
$replica replicaof $master_host $master_port
|
||||
wait_for_sync $replica
|
||||
|
||||
# Run the blocking script on replica
|
||||
set rd [redis_deferring_client]
|
||||
if {$type eq "script"} {
|
||||
$rd eval {while true do end} 0
|
||||
} else {
|
||||
$rd fcall_ro blockfunc 0
|
||||
}
|
||||
|
||||
# Verify replica replies with BUSY
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$replica ping} e] == 1 && [string match {*BUSY*} $e]
|
||||
} else {
|
||||
fail "$type didn't become busy"
|
||||
}
|
||||
|
||||
# Fills client output buffer and triggers fullsync
|
||||
populate 5 bigkey 1000000 -1
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 sync_full] >= 2
|
||||
} else {
|
||||
fail "Fullsync was not triggered"
|
||||
}
|
||||
|
||||
# Verify replica is still running the function
|
||||
after 1000
|
||||
catch {$replica ping} e
|
||||
assert_match {*BUSY*} $e "replica should still reply with BUSY"
|
||||
|
||||
if {$type eq "script"} {
|
||||
$replica script kill
|
||||
} else {
|
||||
$replica function kill
|
||||
}
|
||||
|
||||
# Verify replica is responsive again
|
||||
catch {$rd read} result
|
||||
$rd close
|
||||
wait_for_sync $replica
|
||||
assert_equal [$replica ping] "PONG"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue