From 9f3b445fef783b584b9bd201a04112e534eb92d8 Mon Sep 17 00:00:00 2001 From: Giuliano Bellini s294739 Date: Fri, 9 Feb 2024 00:33:47 +0100 Subject: [PATCH] feat: profanity check service names; completely tested get_service --- Cargo.lock | 44 ++++ Cargo.toml | 2 + build.rs | 106 +++++++++ src/gui/pages/overview_page.rs | 2 +- src/networking/manage_packets.rs | 376 ++++++++++++++++++++++++++++++- 5 files changed, 519 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2f798aa..fbbb32f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -723,12 +723,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -843,6 +855,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "flate2" version = "1.0.28" @@ -1661,6 +1679,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -3087,6 +3114,22 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustrict" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec1a563a7b388fb66b4947102848ce38e4f5adb70a544fdf0ebff0b890b1c88" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "doc-comment", + "finl_unicode", + "itertools", + "lazy_static", + "rustc-hash", + "unicode-normalization", +] + [[package]] name = "rustybuzz" version = "0.7.0" @@ -3394,6 +3437,7 @@ dependencies = [ "rfd", "rodio", "rstest", + "rustrict", "serde", "serde_test", "serial_test", diff --git a/Cargo.toml b/Cargo.toml index e75f5b14..c6f69d9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,8 @@ serial_test = { version = "3.0.0", default_features = false } [build-dependencies] phf_codegen = "0.11.2" phf_shared = "0.11.2" +rustrict = { version = "0.7.21", default-features = false, features = ["censor"] } +once_cell = "1.19.0" [target."cfg(windows)".build-dependencies] winres = "0.1.12" diff --git a/build.rs b/build.rs index 63f59b35..c205afab 100644 --- a/build.rs +++ b/build.rs @@ -6,6 +6,9 @@ use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::Path; +use once_cell::sync::Lazy; +use rustrict::{Censor, Trie, Type}; + include!("./src/networking/types/service_query.rs"); include!("./src/networking/types/protocol.rs"); @@ -68,6 +71,14 @@ fn get_valid_service_fmt_const(s: &str) -> String { { panic!("Invalid service name found: {invalid}") } + inappropriate + if Censor::from_str(inappropriate) + .with_trie(&SAFE_WORDS_FOR_SERVICE_NAME) + .analyze() + .is(Type::INAPPROPRIATE) => + { + panic!("Inappropriate service name found: {inappropriate}") + } name => format!("Service::Name(\"{name}\")"), } } @@ -84,3 +95,98 @@ fn get_valid_service_query(s: &str) -> ServiceQuery { assert!(parts.next().is_none()); ServiceQuery(port, protocol) } + +pub static SAFE_WORDS_FOR_SERVICE_NAME: Lazy = Lazy::new(|| { + let mut safe_words = Trie::default(); + for word in [ + "npp", + "emfis-cntl", + "ardus-cntl", + "pmip6-cntl", + "mpp", + "ipp", + "vpp", + "epp", + "kink", + "kvm-via-ip", + "dpp", + "slinkysearch", + "alta-ana-lm", + "vpps-qua", + "vpps-via", + "ibm-pps", + "ppsms", + "ppsuitemsg", + "icpps", + "rap-listen", + "cadabra-lm", + "pay-per-view", + "sixtrak", + "cvmon", + "houdini-lm", + "dic-aida", + "p2pq", + "bigbrother", + "bintec-admin", + "zymed-zpp", + "cvmmon", + "btpp2sectrans", + "conclave-cpp", + "btpp2audctr1", + "tclprodebugger", + "bintec-capi", + "bintec-tapi", + "dicom-iscl", + "dicom-tls", + "nmsigport", + "ppp", + "tl1-telnet", + "opcon-xps", + "netwatcher-mon", + "netwatcher-db", + "xnm-ssl", + "edm-mgr-cntrl", + "isoft-p2p", + "must-p2p", + "p2pgroup", + "quasar-server", + "int-rcv-cntrl", + "faxstfx-port", + "sunlps-http", + "fagordnc", + "p2pcommunity", + "minger", + "assuria-slm", + "wcpp", + "plcy-net-svcs", + "assyst-dr", + "mobile-p2p", + "assuria-ins", + "taep-as-svc", + "nlg-data", + "dj-ice", + "x500ms", + "X11:7", + "p2p-sip", + "p4p-portal", + "bmc-perf-agent", + "ntz-p2p-storage", + "citrixupp", + "freezexservice", + "p2pevolvenet", + "papachi-p2p-srv", + "espeasy-p2p", + "pim-port", + "vp2p", + "dicom", + "icpp", + "sauterdongle", + "vocaltec-hos", + "BackOrifice", + "dhanalakshmi", + "3gpp-w1ap", + ] { + safe_words.set(word, Type::SAFE); + } + safe_words +}); diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 6d003afb..d1fc56f6 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -682,7 +682,7 @@ fn get_bars_length( MIN_BAR_LENGTH }; } - if outgoing_bar_len > 0.0 && outgoing_bar_len < 3.0 { + if outgoing_bar_len > 0.0 && outgoing_bar_len < MIN_BAR_LENGTH { outgoing_bar_len = if incoming_bar_len > 0.0 { MIN_BAR_LENGTH / 2.0 } else { diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 3a293d9d..a47bb5c0 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -156,7 +156,7 @@ fn analyze_transport_header( } pub fn get_service(key: &AddressPortPair, traffic_direction: TrafficDirection) -> Service { - if key.port1.is_none() || key.port2.is_none() { + if key.port1.is_none() || key.port2.is_none() || key.protocol == Protocol::ICMP { return Service::NotApplicable; } @@ -175,30 +175,29 @@ pub fn get_service(key: &AddressPortPair, traffic_direction: TrafficDirection) - let port1 = key.port1.unwrap(); let port2 = key.port2.unwrap(); + let unknown = Service::Unknown; let service1 = SERVICES .get(&ServiceQuery(port1, key.protocol)) - .cloned() - .unwrap_or_default(); + .unwrap_or(&unknown); let service2 = SERVICES .get(&ServiceQuery(port2, key.protocol)) - .cloned() - .unwrap_or_default(); + .unwrap_or(&unknown); let score1 = compute_service_score( - &service1, + service1, port1, traffic_direction.ne(&TrafficDirection::Outgoing), ); let score2 = compute_service_score( - &service2, + service2, port2, traffic_direction.eq(&TrafficDirection::Outgoing), ); if score1 > score2 { - service1 + service1.clone() } else { - service2 + service2.clone() } } @@ -598,8 +597,10 @@ mod tests { use pcap::Address; use crate::networking::manage_packets::{ - get_traffic_direction, get_traffic_type, is_local_connection, mac_from_dec_to_hex, + get_service, get_traffic_direction, get_traffic_type, is_local_connection, + mac_from_dec_to_hex, }; + use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::service_query::ServiceQuery; use crate::networking::types::traffic_direction::TrafficDirection; use crate::networking::types::traffic_type::TrafficType; @@ -1072,6 +1073,243 @@ fn is_local_connection_ipv6_link_local_test() { assert_eq!(result3, false); } + #[test] + fn test_get_service_simple_only_one_valid() { + let unknown_port = Some(65000); + for p in [Protocol::TCP, Protocol::UDP] { + assert!(SERVICES + .get(&ServiceQuery(unknown_port.unwrap(), p)) + .is_none()); + for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { + let key = AddressPortPair::new( + String::new(), + unknown_port, + String::new(), + unknown_port, + p, + ); + assert_eq!(get_service(&key, d), Service::Unknown); + + for (p1, p2) in [ + (unknown_port, Some(22)), + (Some(22), unknown_port), + (Some(22), Some(22)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("ssh")); + } + + for (p1, p2) in [ + (unknown_port, Some(443)), + (Some(443), unknown_port), + (Some(443), Some(443)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("https")); + } + + for (p1, p2) in [ + (unknown_port, Some(80)), + (Some(80), unknown_port), + (Some(80), Some(80)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("http")); + } + + for (p1, p2) in [ + (unknown_port, Some(1900)), + (Some(1900), unknown_port), + (Some(1900), Some(1900)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("upnp")); + } + } + } + } + + #[test] + fn test_get_service_well_known_ports_always_win() { + let valid_but_not_well_known = Some(1030); + for p in [Protocol::TCP, Protocol::UDP] { + assert_eq!( + SERVICES + .get(&ServiceQuery(valid_but_not_well_known.unwrap(), p)) + .unwrap(), + &Service::Name("iad1") + ); + for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { + let key = AddressPortPair::new( + String::new(), + valid_but_not_well_known, + String::new(), + valid_but_not_well_known, + p, + ); + assert_eq!(get_service(&key, d), Service::Name("iad1")); + + for (p1, p2) in [ + (valid_but_not_well_known, Some(67)), + (Some(67), valid_but_not_well_known), + (Some(67), Some(67)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("dhcps")); + } + + for (p1, p2) in [ + (valid_but_not_well_known, Some(179)), + (Some(179), valid_but_not_well_known), + (Some(179), Some(179)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("bgp")); + } + + for (p1, p2) in [ + (valid_but_not_well_known, Some(53)), + (Some(53), valid_but_not_well_known), + (Some(53), Some(53)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("domain")); + } + + for (p1, p2) in [ + (valid_but_not_well_known, Some(1022)), + (Some(1022), valid_but_not_well_known), + (Some(1022), Some(1022)), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("exp2")); + } + } + } + } + + #[test] + fn test_get_service_direction_bonus_matters() { + let smtp = Some(25); + let tacacs = Some(49); + let netmagic = Some(1196); + let tgp = Some(1223); + + for p in [Protocol::TCP, Protocol::UDP] { + for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { + for (p1, p2) in [(smtp, tacacs), (tacacs, smtp)] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!( + get_service(&key, d), + Service::Name(match (p1, d) { + (source, TrafficDirection::Incoming) if source == tacacs => "tacacs", + (source, TrafficDirection::Outgoing) if source == tacacs => "smtp", + (source, TrafficDirection::Incoming) if source == smtp => "smtp", + (source, TrafficDirection::Outgoing) if source == smtp => "tacacs", + _ => panic!(), + }) + ); + } + + for (p1, p2) in [(netmagic, tgp), (tgp, netmagic)] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!( + get_service(&key, d), + Service::Name(match (p1, d) { + (source, TrafficDirection::Incoming) if source == netmagic => + "netmagic", + (source, TrafficDirection::Outgoing) if source == netmagic => "tgp", + (source, TrafficDirection::Incoming) if source == tgp => "tgp", + (source, TrafficDirection::Outgoing) if source == tgp => "netmagic", + _ => panic!(), + }) + ); + } + } + } + } + + #[test] + fn test_get_service_different_tcp_udp() { + for p in [Protocol::TCP, Protocol::UDP] { + for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { + let key = + AddressPortPair::new(String::new(), Some(5353), String::new(), Some(5353), p); + assert_eq!( + get_service(&key, d), + Service::Name(match p { + Protocol::TCP => "mdns", + Protocol::UDP => "zeroconf", + Protocol::ICMP => panic!(), + }) + ); + + let key = AddressPortPair::new(String::new(), Some(15), String::new(), Some(15), p); + assert_eq!( + get_service(&key, d), + match p { + Protocol::TCP => Service::Name("netstat"), + Protocol::UDP => Service::Unknown, + Protocol::ICMP => panic!(), + } + ); + + let key = + AddressPortPair::new(String::new(), Some(64738), String::new(), Some(64738), p); + assert_eq!( + get_service(&key, d), + match p { + Protocol::TCP => Service::Unknown, + Protocol::UDP => Service::Name("murmur"), + Protocol::ICMP => panic!(), + } + ); + + for (p1, p2) in [(Some(5353), Some(53)), (Some(53), Some(5353))] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Name("domain")); + } + } + } + } + + #[test] + fn test_get_service_not_applicable() { + for p in Protocol::ALL { + for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { + for (p1, p2) in [(None, Some(443)), (None, None), (Some(443), None)] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::NotApplicable); + } + } + } + } + + #[test] + fn test_get_service_unknown() { + let unknown_port_1 = Some(39332); + let unknown_port_2 = Some(23679); + for p in [Protocol::TCP, Protocol::UDP] { + assert!(SERVICES + .get(&ServiceQuery(unknown_port_1.unwrap(), p)) + .is_none()); + assert!(SERVICES + .get(&ServiceQuery(unknown_port_2.unwrap(), p)) + .is_none()); + for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { + for (p1, p2) in [ + (unknown_port_1, unknown_port_2), + (unknown_port_2, unknown_port_1), + (unknown_port_1, unknown_port_1), + (unknown_port_2, unknown_port_2), + ] { + let key = AddressPortPair::new(String::new(), p1, String::new(), p2, p); + assert_eq!(get_service(&key, d), Service::Unknown); + } + } + } + } + #[test] fn test_all_services_map_key_and_values_are_valid() { assert_eq!(SERVICES.len(), 12066); @@ -1299,4 +1537,122 @@ fn test_service_names_of_old_application_protocols() { &Service::Name("zeroconf") ); } + + #[test] + fn test_other_service_names() { + for p in [Protocol::TCP, Protocol::UDP] { + assert!(SERVICES.get(&ServiceQuery(4, p)).is_none()); + assert!(SERVICES.get(&ServiceQuery(6, p)).is_none()); + assert_eq!( + SERVICES.get(&ServiceQuery(7, p)).unwrap(), + &Service::Name("echo") + ); + assert!(SERVICES.get(&ServiceQuery(5811, p)).is_none()); + assert_eq!( + SERVICES.get(&ServiceQuery(6004, p)).unwrap(), + &Service::Name("X11:4") + ); + assert_eq!( + SERVICES.get(&ServiceQuery(7777, p)).unwrap(), + &Service::Name("cbt") + ); + assert!(SERVICES.get(&ServiceQuery(65000, p)).is_none()); + } + + assert_eq!( + SERVICES.get(&ServiceQuery(15, Protocol::TCP)).unwrap(), + &Service::Name("netstat") + ); + assert!(SERVICES.get(&ServiceQuery(15, Protocol::UDP)).is_none()); + + assert_eq!( + SERVICES.get(&ServiceQuery(26, Protocol::TCP)).unwrap(), + &Service::Name("rsftp") + ); + assert!(SERVICES.get(&ServiceQuery(26, Protocol::UDP)).is_none()); + + assert_eq!( + SERVICES.get(&ServiceQuery(87, Protocol::TCP)).unwrap(), + &Service::Name("priv-term-l") + ); + assert!(SERVICES.get(&ServiceQuery(87, Protocol::UDP)).is_none()); + + assert_eq!( + SERVICES.get(&ServiceQuery(106, Protocol::TCP)).unwrap(), + &Service::Name("pop3pw") + ); + assert_eq!( + SERVICES.get(&ServiceQuery(106, Protocol::UDP)).unwrap(), + &Service::Name("3com-tsmux") + ); + + assert!(SERVICES.get(&ServiceQuery(1028, Protocol::TCP)).is_none()); + assert_eq!( + SERVICES.get(&ServiceQuery(1028, Protocol::UDP)).unwrap(), + &Service::Name("ms-lsa") + ); + + assert_eq!( + SERVICES.get(&ServiceQuery(1029, Protocol::TCP)).unwrap(), + &Service::Name("ms-lsa") + ); + assert_eq!( + SERVICES.get(&ServiceQuery(1029, Protocol::UDP)).unwrap(), + &Service::Name("solid-mux") + ); + + assert_eq!( + SERVICES.get(&ServiceQuery(5820, Protocol::TCP)).unwrap(), + &Service::Name("autopassdaemon") + ); + assert!(SERVICES.get(&ServiceQuery(5820, Protocol::UDP)).is_none()); + + assert_eq!( + SERVICES.get(&ServiceQuery(5900, Protocol::TCP)).unwrap(), + &Service::Name("vnc") + ); + assert_eq!( + SERVICES.get(&ServiceQuery(5900, Protocol::UDP)).unwrap(), + &Service::Name("rfb") + ); + + assert_eq!( + SERVICES.get(&ServiceQuery(5938, Protocol::TCP)).unwrap(), + &Service::Name("teamviewer") + ); + assert!(SERVICES.get(&ServiceQuery(5938, Protocol::UDP)).is_none()); + + assert_eq!( + SERVICES.get(&ServiceQuery(8888, Protocol::TCP)).unwrap(), + &Service::Name("sun-answerbook") + ); + assert_eq!( + SERVICES.get(&ServiceQuery(8888, Protocol::UDP)).unwrap(), + &Service::Name("ddi-udp-1") + ); + + assert_eq!( + SERVICES.get(&ServiceQuery(23294, Protocol::TCP)).unwrap(), + &Service::Name("5afe-dir") + ); + assert!(SERVICES.get(&ServiceQuery(23294, Protocol::UDP)).is_none()); + + assert!(SERVICES.get(&ServiceQuery(48899, Protocol::TCP)).is_none()); + assert_eq!( + SERVICES.get(&ServiceQuery(48899, Protocol::UDP)).unwrap(), + &Service::Name("tc_ads_discovery") + ); + + assert_eq!( + SERVICES.get(&ServiceQuery(62078, Protocol::TCP)).unwrap(), + &Service::Name("iphone-sync") + ); + assert!(SERVICES.get(&ServiceQuery(62078, Protocol::UDP)).is_none()); + + assert!(SERVICES.get(&ServiceQuery(64738, Protocol::TCP)).is_none()); + assert_eq!( + SERVICES.get(&ServiceQuery(64738, Protocol::UDP)).unwrap(), + &Service::Name("murmur") + ); + } }