harden round 3 — overflow, DoS, correctness fixes - #93
Merged
Conversation
EXPIRE seconds * 1000 and PEXPIRE now_ms() + millis could overflow u64 from user-supplied values. expiry_from_duration also truncated u128 to u64 without clamping. all three sites now use saturating_mul and saturating_add.
-i64::MIN overflows in both debug (panic) and release (wraps). shard.rs now uses checked_neg() and returns an overflow error. recovery.rs uses saturating_neg() for safe AOF replay.
Buf::get_* methods panic on insufficient data. replaced all bare get_u8/get_u16_le/get_u64_le calls in decode paths with safe_get_* helpers that return io::Error. also caps member/update/slot counts at 1024 to prevent allocation bombs from crafted packets.
a flat array with millions of tiny null elements could amplify ~3 bytes of wire data into ~32 bytes of heap per element. adds MAX_ARRAY_ELEMENTS (1M) for arrays/maps and MAX_BULK_LEN (512MB, matching redis) for bulk strings.
the subscriber loop was missing both MAX_BUF_SIZE and IDLE_TIMEOUT guards that the main connection loop has. a client in subscriber mode could grow the buffer without bound or hold a connection slot forever.
when all fields are removed from a hash or all members from a set, the key is auto-deleted but expiry_count was not decremented if the key had a TTL. matches the existing correct behavior in zrem and list_pop.
acceptor.accept() blocked indefinitely, allowing a slowloris attack on the TLS port to exhaust connection semaphore permits. both sharded and concurrent mode TLS paths now timeout after 10 seconds.
kacy
added a commit
that referenced
this pull request
Feb 19, 2026
* fix: use saturating arithmetic for expire/pexpire timestamps EXPIRE seconds * 1000 and PEXPIRE now_ms() + millis could overflow u64 from user-supplied values. expiry_from_duration also truncated u128 to u64 without clamping. all three sites now use saturating_mul and saturating_add. * fix: prevent decrby panic on i64::MIN negation -i64::MIN overflows in both debug (panic) and release (wraps). shard.rs now uses checked_neg() and returns an overflow error. recovery.rs uses saturating_neg() for safe AOF replay. * fix: prevent panics from truncated gossip messages Buf::get_* methods panic on insufficient data. replaced all bare get_u8/get_u16_le/get_u64_le calls in decode paths with safe_get_* helpers that return io::Error. also caps member/update/slot counts at 1024 to prevent allocation bombs from crafted packets. * fix: add element count and bulk string length limits to parser a flat array with millions of tiny null elements could amplify ~3 bytes of wire data into ~32 bytes of heap per element. adds MAX_ARRAY_ELEMENTS (1M) for arrays/maps and MAX_BULK_LEN (512MB, matching redis) for bulk strings. * fix: add buffer limit and idle timeout to subscriber mode the subscriber loop was missing both MAX_BUF_SIZE and IDLE_TIMEOUT guards that the main connection loop has. a client in subscriber mode could grow the buffer without bound or hold a connection slot forever. * fix: decrement expiry_count when hdel/srem auto-deletes empty key when all fields are removed from a hash or all members from a set, the key is auto-deleted but expiry_count was not decremented if the key had a TTL. matches the existing correct behavior in zrem and list_pop. * fix: add 10s timeout to TLS handshake acceptor.accept() blocked indefinitely, allowing a slowloris attack on the TLS port to exhaust connection semaphore permits. both sharded and concurrent mode TLS paths now timeout after 10 seconds. * fix: reject RENAME when keys hash to different shards * fix: use saturating_add for gossip incarnation counter * fix: cap pre-allocation from untrusted count fields in aof and snapshot parsing * fix: fsync parent directory after snapshot atomic rename * chore: cargo fmt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
summary
third round of hardening from a full codebase audit. addresses 11 issues across 5 crates:
high severity:
medium severity:
what was tested
cargo test -p emberkv-core -p ember-persistence -p ember-server -p ember-cluster -p ember-protocol— all passcargo clippy --workspace -- -D warnings— cleancargo fmt --all -- --check— cleandesign considerations
Engine::same_shard()method rather than exposing the internalshard_indexfunction. concurrent mode (DashMap) doesn't need this check since it's thread-safe.let _ =) — a failure here shouldn't abort the snapshot since the data is already on disk.