From cd6cd46b9070772611a3567e853bd1ff6e3ff17c Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Sat, 14 Feb 2026 17:34:47 -0500 Subject: [PATCH] reject infinity in parse_f64, validate cluster slot ranges, bound vector parsing eagerly - parse_f64 now rejects inf/-inf (was only rejecting NaN), making it consistent with parse_incrbyfloat which already had this check - parse_slot_list and parse_cluster_setslot now validate slot < 16384 - parse_vadd/parse_vsim check MAX_VECTOR_DIMS inside the loop instead of after full allocation, preventing unnecessary memory usage --- crates/ember-protocol/src/command.rs | 42 ++++++++++++++++------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/crates/ember-protocol/src/command.rs b/crates/ember-protocol/src/command.rs index 9fa3fb6e..be62dfc2 100644 --- a/crates/ember-protocol/src/command.rs +++ b/crates/ember-protocol/src/command.rs @@ -1189,9 +1189,9 @@ fn parse_f64(frame: &Frame, cmd: &str) -> Result { let v = s.parse::().map_err(|_| { ProtocolError::InvalidCommandFrame(format!("value is not a valid float for '{cmd}'")) })?; - if v.is_nan() { + if v.is_nan() || v.is_infinite() { return Err(ProtocolError::InvalidCommandFrame(format!( - "NaN is not a valid score for '{cmd}'" + "value is not a valid finite float for '{cmd}'" ))); } Ok(v) @@ -1643,6 +1643,11 @@ fn parse_slot_list(args: &[Frame]) -> Result, ProtocolError> { let slot: u16 = slot_str .parse() .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?; + if slot >= 16384 { + return Err(ProtocolError::InvalidCommandFrame(format!( + "invalid slot {slot}: must be 0-16383" + ))); + } slots.push(slot); } Ok(slots) @@ -1657,6 +1662,11 @@ fn parse_cluster_setslot(args: &[Frame]) -> Result { let slot: u16 = slot_str .parse() .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?; + if slot >= 16384 { + return Err(ProtocolError::InvalidCommandFrame(format!( + "invalid slot {slot}: must be 0-16383" + ))); + } if args.len() < 2 { return Err(ProtocolError::WrongArity("CLUSTER SETSLOT".into())); @@ -1825,10 +1835,15 @@ fn parse_vadd(args: &[Frame]) -> Result { let key = extract_string(&args[0])?; let element = extract_string(&args[1])?; - // parse vector values until we hit a non-numeric argument or end + // parse vector values until we hit a non-numeric argument, end, or dim limit let mut idx = 2; let mut vector = Vec::new(); while idx < args.len() { + if vector.len() >= MAX_VECTOR_DIMS { + return Err(ProtocolError::InvalidCommandFrame(format!( + "VADD: vector exceeds {MAX_VECTOR_DIMS} dimensions" + ))); + } let s = extract_string(&args[idx])?; if let Ok(v) = s.parse::() { vector.push(v); @@ -1844,13 +1859,6 @@ fn parse_vadd(args: &[Frame]) -> Result { )); } - if vector.len() > MAX_VECTOR_DIMS { - return Err(ProtocolError::InvalidCommandFrame(format!( - "VADD: vector has {} dimensions, max is {MAX_VECTOR_DIMS}", - vector.len() - ))); - } - // parse optional flags let mut metric: u8 = 0; // cosine default let mut quantization: u8 = 0; // f32 default @@ -1960,10 +1968,15 @@ fn parse_vsim(args: &[Frame]) -> Result { let key = extract_string(&args[0])?; - // parse query vector until we hit a non-numeric argument + // parse query vector until we hit a non-numeric argument, end, or dim limit let mut idx = 1; let mut query = Vec::new(); while idx < args.len() { + if query.len() >= MAX_VECTOR_DIMS { + return Err(ProtocolError::InvalidCommandFrame(format!( + "VSIM: query exceeds {MAX_VECTOR_DIMS} dimensions" + ))); + } let s = extract_string(&args[idx])?; if let Ok(v) = s.parse::() { query.push(v); @@ -1979,13 +1992,6 @@ fn parse_vsim(args: &[Frame]) -> Result { )); } - if query.len() > MAX_VECTOR_DIMS { - return Err(ProtocolError::InvalidCommandFrame(format!( - "VSIM: query has {} dimensions, max is {MAX_VECTOR_DIMS}", - query.len() - ))); - } - // COUNT k is required let mut count: Option = None; let mut ef_search: usize = 0;