fix(rooms): harden chat-room parsing and TUI state from review findings
Adversarial review of the chat-rooms feature surfaced seven confirmed issues; all fixed with regression tests: lib (DoS hardening — a malicious/MITM server could send a tiny frame with a ~4-billion element count): - room_list: bound the name/count vector loops by remaining payload. - join_room: bound the member-list loop the same way. TUI (soulseek-rs/src/models/rooms.rs, ui): - a message for a room we already left no longer resurrects its tab (route via open_index, not ensure_open). - closing a tab now clears the unread badge on the room we land on. - a room-list refresh keeps the highlight on the same room across a re-sort (track selection by name, not index). - Esc in the room list clears a lingering filter before closing, matching the title's 'Esc: clear' hint. - chat log renders one row per line so the newest message is never clipped by wrapping.
This commit is contained in:
@@ -27,11 +27,20 @@ pub fn parse_room_list(message: &mut Message) -> Vec<RoomInfo> {
|
||||
let name_count = message.read_int32();
|
||||
let mut names = Vec::new();
|
||||
for _ in 0..name_count {
|
||||
// Stop once the payload can't hold another field: a bogus (possibly
|
||||
// hostile) count must not spin us into an OOM allocation loop. A
|
||||
// 4-byte floor also avoids read_int32 stalling on a 1-3 byte tail.
|
||||
if message.get_pointer() + 4 > message.get_size() {
|
||||
break;
|
||||
}
|
||||
names.push(message.read_string());
|
||||
}
|
||||
let count_count = message.read_int32();
|
||||
let mut counts = Vec::new();
|
||||
for _ in 0..count_count {
|
||||
if message.get_pointer() + 4 > message.get_size() {
|
||||
break;
|
||||
}
|
||||
counts.push(message.read_int32());
|
||||
}
|
||||
names
|
||||
@@ -90,6 +99,17 @@ mod tests {
|
||||
assert!(parse_room_list(&mut message).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_counts_do_not_hang_or_overallocate() {
|
||||
// A tiny frame claiming ~4 billion names/counts must return promptly
|
||||
// (bounded by the payload) rather than looping into an OOM.
|
||||
let mut message = framed(|m| {
|
||||
m.write_int32(u32::MAX);
|
||||
});
|
||||
let rooms = parse_room_list(&mut message);
|
||||
assert!(rooms.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handler_forwards_parsed_rooms() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
Reference in New Issue
Block a user