Skip to content

Security: fix critical vulnerabilities (v0.11.5) - #37

Merged
mack42 merged 5 commits into
mainfrom
security/critical-fixes-0.11.5
Jul 16, 2026
Merged

Security: fix critical vulnerabilities (v0.11.5)#37
mack42 merged 5 commits into
mainfrom
security/critical-fixes-0.11.5

Conversation

@mack42

@mack42 mack42 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes the six Critical findings from the security audit. Version bumped to 0.11.5. cargo check is clean (no warnings); all unit + integration tests pass except two pubsub integration tests in the untracked WIP tests/ dir that fail identically on main (pre-existing, not touched here).

Findings addressed

1. ACL enforcement was non-functional. Runtime ACL SETUSER wrote to a static registry while the auth/enforcement path read a separate startup-only snapshot, so no runtime rule was ever enforced and ACL-created users could never authenticate. The acl.rs registry is now the single source of truth; the connection and HTTP layers read/write it via new public functions. Removed the disconnected SharedState.acl_users map and the dead AclUser/AclMatch types.

2. HTTP /api/v1/command ran arbitrary commands with no authorization. check_auth now resolves the bearer token to a username, and the command + key endpoints enforce the same per-user command and key-pattern ACL as the RESP path (403 on denial).

3. Scripting engine could DoS the server and bypass read-only intent. Added a 256 MB Lua memory limit and a 5 s wall-clock timeout (instruction hook), blocked nested EVAL/EVALSHA/FCALL/FUNCTION/SCRIPT (unbounded recursion → stack-overflow abort), and made EVAL_RO/EVALSHA_RO/FCALL_RO genuinely reject write commands.

4. Attacker-controlled allocations (multi-GB/TB OOM). SETBIT/BITFIELD reject bit offsets past 512 MB (checked #-form multiply); SETRANGE caps result length with checked_add; CMS.INITBYDIM/INITBYPROB and TOPK.RESERVE validate width × depth × 8 ≤ 256 MB with checked arithmetic.

5. Unbounded per-connection query buffer. Capped at 1 GB; oversized/incomplete frames get an error + disconnect.

6. AOF replay could OOM/panic on a tampered file. Multibulk count (≤ 1,048,576) and bulk length (≤ 512 MB) are validated before allocation.

Known residual

Per-user ACL attribution of individual redis.call() invocations inside a script is still not wired (CommandContext carries no user identity — a broader refactor). Mitigated: running EVAL is ACL-gated at the connection/HTTP layer and the _RO variants are now truly read-only. Suggested as a follow-up.

High/Medium/Low findings from the audit are intentionally out of scope for this PR.

mack42 added 5 commits May 13, 2026 06:52
Closes #31 #35 #36.

#31 CONFIG SET
- Stop returning OK without applying changes. Return ERR explaining
  the parameter must be set via the matching CLI flag at startup.
  Silent no-op was actively misleading.

#35 CI hygiene
- Trim tokio features from "full" to the eight actually used
  (rt-multi-thread, net, io-util, sync, time, macros, signal, fs)
- Trim hyper to ["server", "http1"]; hyper-util to ["tokio"]
- Tag legitimate-but-unused API surface with #[allow(dead_code)]
  so #![warn(dead_code)] at the crate root catches accidental
  dead code from now on
- Add .github/workflows/ci.yml running cargo check + clippy
  (-D warnings) + test + fmt on every PR and push to main

#36 Low-impact perf cleanup
- maxmemory_policy: borrow instead of cloning the String per write
- ZRANGEBYSCORE: fold take_while + filter into a single filter_map
- GETRANGE: zero-copy via Bytes::slice instead of slice.to_vec()
- MGET: iterate args by index instead of building an intermediate Vec

Bump version 0.11.0 -> 0.11.1.
Closes #30 #32 #33.

#30 AUTH brute-force protection
- Per-connection consecutive-failure counter, reset on success
- After 5 failures, exponential backoff (100ms doubling, capped 5s)
  applied before returning WRONGPASS
- handle_auth / handle_auth_hello are now async and consult ACL via
  proper .await locking instead of try_lock (the old try_lock could
  spuriously fail under contention and report WRONGPASS)
- Extract AclMatch enum to clean up the auth flow

#32 Information-disclosure cleanup
- Skip recording latency stats and command-name entries when the
  response is NOAUTH or WRONGPASS; unauthenticated probes can no
  longer populate the histograms visible via INFO commandstats /
  LATENCY HISTORY

#33 Pub/Sub dead-sender cleanup
- PubSubManager::publish now takes &mut self and prunes any client
  whose Sender returns an error from send() (channel closed). This
  removes the slow leak on abrupt-disconnect paths where the normal
  cleanup_pubsub didn't run

Bump version 0.11.1 -> 0.11.2.
Closes #27 (with follow-up note below).

Add src/command/parse.rs with strict numeric helpers (int, u64_, usize_,
float) that use std::str::from_utf8 instead of from_utf8_lossy. The old
chain allocated a Cow + an owned String per call and silently accepted
malformed UTF-8 in numeric arguments.

96 sites converted across:
- sorted_set.rs (12), list.rs (14), geo.rs (14)
- strings.rs (13), bitmap.rs (8)
- stream.rs (9), keys.rs (5), set.rs (3)
- server_cmds.rs (3), hash.rs (3), cluster.rs (3)
- scripting.rs (3), scan.rs (4), advanced.rs (1), json.rs (1)

Sites left in place (~20):
- `.parse().unwrap_or(...)` defaulting calls (no error path to migrate)
- `parse::<u8>()` in bitmap.rs (no u8 helper yet)
- Non-template variants inside nested closures and Result-returning
  helpers — these need per-site review and were out of scope here

Bump version 0.11.2 -> 0.11.3.
Closes #29.

SortedSetData.scores was BTreeMap<ScoreKey, Bytes> where the value was
always Bytes::new() — a per-insert no-op allocation kept around so the
type could be a Map. Migrate to BTreeSet<ScoreKey>, which expresses what
the code actually wanted (an ordered set of keys) and removes:

- one Bytes::new() per insert
- the (_, _) tuple destructuring in iter/range/skip paths

Iter patterns shift from |(k, _)| -> |k|. .keys() becomes .iter().
External callers (http.rs serializer) updated accordingly.

Bump version 0.11.3 -> 0.11.4.
Unify ACL enforcement, gate HTTP command execution, sandbox scripting,
and bound attacker-controlled allocations.

- ACL: make the acl.rs registry the single source of truth so runtime
  ACL SETUSER changes are actually enforced; drop the disconnected
  SharedState.acl_users snapshot and dead AclUser/AclMatch types.
- HTTP: resolve the bearer token to a user and enforce per-user command
  and key ACLs on /api/v1/command and the key endpoints (403 on denial).
- Scripting: add a 256MB memory limit and 5s wall-clock timeout, block
  nested EVAL/FCALL/SCRIPT/FUNCTION (recursion crash), and make the _RO
  variants reject write commands.
- Allocation caps: bound SETBIT/BITFIELD bit offsets and SETRANGE length
  (512MB) with checked arithmetic; cap CMS/TopK width*depth*8 at 256MB.
- Connection: cap the per-connection query buffer at 1GB.
- AOF: bound multibulk count and bulk length before allocation so a
  tampered appendonly.aof cannot OOM/panic on startup.
@mack42 mack42 self-assigned this Jul 16, 2026
@mack42
mack42 merged commit a717c64 into main Jul 16, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant