summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/integration/corrupt-dump-fuzzer.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/tests/integration/corrupt-dump-fuzzer.tcl')
-rw-r--r--examples/redis-unstable/tests/integration/corrupt-dump-fuzzer.tcl261
1 files changed, 0 insertions, 261 deletions
diff --git a/examples/redis-unstable/tests/integration/corrupt-dump-fuzzer.tcl b/examples/redis-unstable/tests/integration/corrupt-dump-fuzzer.tcl
deleted file mode 100644
index 5c7c992..0000000
--- a/examples/redis-unstable/tests/integration/corrupt-dump-fuzzer.tcl
+++ /dev/null
@@ -1,261 +0,0 @@
-# tests of corrupt listpack payload with valid CRC
-
-# The fuzzer can cause corrupt the state in many places, which could
-# mess up the reply, so we decided to skip logreqres.
-tags {"dump" "corruption" "external:skip" "logreqres:skip"} {
-
-# catch sigterm so that in case one of the random command hangs the test,
-# usually due to redis not putting a response in the output buffers,
-# we'll know which command it was
-if { ! [ catch {
- package require Tclx
-} err ] } {
- signal error SIGTERM
-}
-
-proc generate_collections {suffix elements} {
- set rd [redis_deferring_client]
- set numcmd 7
- set has_vsets [server_has_command vadd]
- if {$has_vsets} {incr numcmd}
-
- for {set j 0} {$j < $elements} {incr j} {
- # add both string values and integers
- if {$j % 2 == 0} {set val $j} else {set val "_$j"}
- $rd hset hash$suffix $j $val
- $rd hset hashmd$suffix $j $val
- $rd hexpire hashmd$suffix [expr {int(rand() * 10000)}] FIELDS 1 $j
- $rd lpush list$suffix $val
- $rd zadd zset$suffix $j $val
- $rd sadd set$suffix $val
- $rd xadd stream$suffix * item 1 value $val
- if {$has_vsets} {
- $rd vadd vset$suffix VALUES 3 1 1 1 $j
- }
- }
- for {set j 0} {$j < $elements * $numcmd} {incr j} {
- $rd read ; # Discard replies
- }
- $rd close
-}
-
-# generate keys with various types and encodings
-proc generate_types {} {
- r config set list-max-ziplist-size 5
- r config set hash-max-ziplist-entries 5
- r config set set-max-listpack-entries 5
- r config set zset-max-ziplist-entries 5
- r config set stream-node-max-entries 5
-
- # create small (ziplist / listpack encoded) objects with 3 items
- generate_collections "" 3
-
- # add some metadata to the stream
- r xgroup create stream mygroup 0
- set records [r xreadgroup GROUP mygroup Alice COUNT 2 STREAMS stream >]
- r xdel stream [lindex [lindex [lindex [lindex $records 0] 1] 1] 0]
- r xack stream mygroup [lindex [lindex [lindex [lindex $records 0] 1] 0] 0]
-
- # create other non-collection types
- r incr int
- r set string str
-
- # create bigger objects with 10 items (more than a single ziplist / listpack)
- generate_collections big 10
-
- # make sure our big stream also has a listpack record that has different
- # field names than the master recorded
- r xadd streambig * item 1 value 1
- r xadd streambig * item 1 unique value
-}
-
-proc corrupt_payload {payload} {
- set len [string length $payload]
- set count 1 ;# usually corrupt only one byte
- if {rand() > 0.9} { set count 2 }
- while { $count > 0 } {
- set idx [expr {int(rand() * $len)}]
- set ch [binary format c [expr {int(rand()*255)}]]
- set payload [string replace $payload $idx $idx $ch]
- incr count -1
- }
- return $payload
-}
-
-# fuzzy tester for corrupt RESTORE payloads
-# valgrind will make sure there were no leaks in the rdb loader error handling code
-foreach sanitize_dump {no yes} {
- if {$::accurate} {
- set min_duration [expr {60 * 10}] ;# run at least 10 minutes
- set min_cycles 1000 ;# run at least 1k cycles (max 16 minutes)
- } else {
- set min_duration 10 ; # run at least 10 seconds
- set min_cycles 10 ; # run at least 10 cycles
- }
-
- # Don't execute this on FreeBSD due to a yet-undiscovered memory issue
- # which causes tclsh to bloat.
- if {[exec uname] == "FreeBSD"} {
- set min_cycles 1
- set min_duration 1
- }
-
- test "Fuzzer corrupt restore payloads - sanitize_dump: $sanitize_dump" {
- if {$min_duration * 2 > $::timeout} {
- fail "insufficient timeout"
- }
- # start a server, fill with data and save an RDB file once (avoid re-save)
- start_server [list overrides [list "save" "" use-exit-on-panic yes crash-memcheck-enabled no loglevel verbose] ] {
- set stdout [srv 0 stdout]
- r config set sanitize-dump-payload $sanitize_dump
- r debug set-skip-checksum-validation 1
- set start_time [clock seconds]
- generate_types
- set dbsize [r dbsize]
- r save
- set cycle 0
- set stat_terminated_in_restore 0
- set stat_terminated_in_traffic 0
- set stat_terminated_by_signal 0
- set stat_successful_restore 0
- set stat_rejected_restore 0
- set stat_traffic_commands_sent 0
- # repeatedly DUMP a random key, corrupt it and try RESTORE into a new key
- while true {
- set k [r randomkey]
- set dump [r dump $k]
- set dump [corrupt_payload $dump]
- set printable_dump [string2printable $dump]
- set restore_failed false
- set report_and_restart false
- set sent {}
- set expired_subkeys [s expired_subkeys]
- # RESTORE can fail, but hopefully not terminate
- if { [catch { r restore "_$k" 0 $dump REPLACE } err] } {
- set restore_failed true
- # skip if return failed with an error response.
- if {[string match "ERR*" $err]} {
- incr stat_rejected_restore
- } else {
- set report_and_restart true
- incr stat_terminated_in_restore
- write_log_line 0 "corrupt payload: $printable_dump"
- if {$sanitize_dump == yes} {
- puts "Server crashed in RESTORE with payload: $printable_dump"
- }
- }
- } else {
- r ping ;# an attempt to check if the server didn't terminate (this will throw an error that will terminate the tests)
- }
-
- set print_commands false
- if {!$restore_failed} {
- # if RESTORE didn't fail or terminate, run some random traffic on the new key
- incr stat_successful_restore
- if { [ catch {
- set type [r type "_$k"]
- if {$type eq {none}} {
- # The key has been removed due to expiration.
- # Ensure the server didn't terminate during expiration and verify
- # expire stats to confirm the key was removed due to expiration.
- r ping
- assert_morethan [s expired_subkeys] $expired_subkeys
- } else {
- set sent [generate_fuzzy_traffic_on_key "_$k" $type 1] ;# traffic for 1 second
- }
-
- incr stat_traffic_commands_sent [llength $sent]
- r del "_$k" ;# in case the server terminated, here's where we'll detect it.
- if {$dbsize != [r dbsize]} {
- puts "unexpected keys"
- puts "keys: [r keys *]"
- puts "commands leading to it:"
- foreach cmd $sent {
- foreach arg $cmd {
- puts -nonewline "[string2printable $arg] "
- }
- puts ""
- }
- exit 1
- }
- } err ] } {
- set err [format "%s" $err] ;# convert to string for pattern matching
- if {[string match "*SIGTERM*" $err]} {
- puts "payload that caused test to hang: $printable_dump"
- if {$::dump_logs} {
- set srv [get_srv 0]
- dump_server_log $srv
- }
- exit 1
- }
- # if the server terminated update stats and restart it
- set report_and_restart true
- incr stat_terminated_in_traffic
- set by_signal [count_log_message 0 "crashed by signal"]
- incr stat_terminated_by_signal $by_signal
-
- if {$by_signal != 0 || $sanitize_dump == yes} {
- if {$::dump_logs} {
- set srv [get_srv 0]
- dump_server_log $srv
- }
-
- puts "Server crashed (by signal: $by_signal, err: $err), with payload: $printable_dump"
- set print_commands true
- }
- }
- }
-
- # check valgrind report for invalid reads after each RESTORE
- # payload so that we have a report that is easier to reproduce
- set valgrind_errors [find_valgrind_errors [srv 0 stderr] false]
- set asan_errors [sanitizer_errors_from_file [srv 0 stderr]]
- if {$valgrind_errors != "" || $asan_errors != ""} {
- puts "valgrind or asan found an issue for payload: $printable_dump"
- set report_and_restart true
- set print_commands true
- }
-
- if {$report_and_restart} {
- if {$print_commands} {
- puts "violating commands:"
- foreach cmd $sent {
- foreach arg $cmd {
- puts -nonewline "[string2printable $arg] "
- }
- puts ""
- }
- }
-
- # restart the server and re-apply debug configuration
- write_log_line 0 "corrupt payload: $printable_dump"
- restart_server 0 true true
- r config set sanitize-dump-payload $sanitize_dump
- r debug set-skip-checksum-validation 1
- }
-
- incr cycle
- if { ([clock seconds]-$start_time) >= $min_duration && $cycle >= $min_cycles} {
- break
- }
- }
- if {$::verbose} {
- puts "Done $cycle cycles in [expr {[clock seconds]-$start_time}] seconds."
- puts "RESTORE: successful: $stat_successful_restore, rejected: $stat_rejected_restore"
- puts "Total commands sent in traffic: $stat_traffic_commands_sent, crashes during traffic: $stat_terminated_in_traffic ($stat_terminated_by_signal by signal)."
- }
- }
- # if we run sanitization we never expect the server to crash at runtime
- if {$sanitize_dump == yes} {
- assert_equal $stat_terminated_in_restore 0
- assert_equal $stat_terminated_in_traffic 0
- }
- # make sure all terminations where due to assertion and not a SIGSEGV
- assert_equal $stat_terminated_by_signal 0
- }
-}
-
-
-
-} ;# tags
-