diff --git a/lib/src/analysis/analyzer.rs b/lib/src/analysis/analyzer.rs index 47fa5aa..c1f8da6 100644 --- a/lib/src/analysis/analyzer.rs +++ b/lib/src/analysis/analyzer.rs @@ -7,7 +7,8 @@ use crate::{diag::MessagesContainer, gsmtap_parser}; use super::{ imsi_requested::ImsiRequestedAnalyzer, information_element::InformationElement, - lte_downgrade::ConnectionRedirect2GDowngradeAnalyzer, + connection_redirect_downgrade::ConnectionRedirect2GDowngradeAnalyzer, + priority_2g_downgrade::LteSib6And7DowngradeAnalyzer, null_cipher::NullCipherAnalyzer, }; @@ -117,8 +118,9 @@ impl Harness { pub fn new_with_all_analyzers() -> Self { let mut harness = Harness::new(); - harness.add_analyzer(Box::new(ConnectionRedirect2GDowngradeAnalyzer{})); harness.add_analyzer(Box::new(ImsiRequestedAnalyzer::new())); + harness.add_analyzer(Box::new(ConnectionRedirect2GDowngradeAnalyzer{})); + harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{})); harness.add_analyzer(Box::new(NullCipherAnalyzer{})); harness diff --git a/lib/src/analysis/lte_downgrade.rs b/lib/src/analysis/connection_redirect_downgrade.rs similarity index 100% rename from lib/src/analysis/lte_downgrade.rs rename to lib/src/analysis/connection_redirect_downgrade.rs diff --git a/lib/src/analysis/mod.rs b/lib/src/analysis/mod.rs index 5b70ed5..755172d 100644 --- a/lib/src/analysis/mod.rs +++ b/lib/src/analysis/mod.rs @@ -1,6 +1,7 @@ pub mod analyzer; pub mod information_element; -pub mod lte_downgrade; +pub mod priority_2g_downgrade; +pub mod connection_redirect_downgrade; pub mod imsi_provided; pub mod imsi_requested; pub mod null_cipher; diff --git a/lib/src/analysis/priority_2g_downgrade.rs b/lib/src/analysis/priority_2g_downgrade.rs new file mode 100644 index 0000000..44003fd --- /dev/null +++ b/lib/src/analysis/priority_2g_downgrade.rs @@ -0,0 +1,81 @@ +use std::borrow::Cow; + +use super::analyzer::{Analyzer, Event, EventType, Severity}; +use super::information_element::{InformationElement, LteInformationElement}; +use telcom_parser::lte_rrc::{BCCH_DL_SCH_MessageType, BCCH_DL_SCH_MessageType_c1, CellReselectionPriority, SystemInformationBlockType7, SystemInformationCriticalExtensions, SystemInformation_r8_IEsSib_TypeAndInfo, SystemInformation_r8_IEsSib_TypeAndInfo_Entry}; + +/// Based on heuristic T7 from Shinjo Park's "Why We Cannot Win". +pub struct LteSib6And7DowngradeAnalyzer { +} + +impl LteSib6And7DowngradeAnalyzer { + fn unpack_system_information<'a>(&self, ie: &'a InformationElement) -> Option<&'a SystemInformation_r8_IEsSib_TypeAndInfo> { + if let InformationElement::LTE(LteInformationElement::BcchDlSch(bcch_dl_sch_message)) = ie { + if let BCCH_DL_SCH_MessageType::C1(BCCH_DL_SCH_MessageType_c1::SystemInformation(system_information)) = &bcch_dl_sch_message.message { + if let SystemInformationCriticalExtensions::SystemInformation_r8(sib) = &system_information.critical_extensions { + return Some(&sib.sib_type_and_info); + } + } + } + None + } +} + +// TODO: keep track of SIB state to compare LTE reselection blocks w/ 2g/3g ones +impl Analyzer for LteSib6And7DowngradeAnalyzer { + fn get_name(&self) -> Cow { + Cow::from("LTE SIB 6/7 Downgrade") + } + + fn get_description(&self) -> Cow { + Cow::from("Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.") + } + + fn analyze_information_element(&mut self, ie: &InformationElement) -> Option { + let sibs = &self.unpack_system_information(ie)?.0; + for sib in sibs { + match sib { + SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib6(sib6) => { + if let Some(carrier_info_list) = sib6.carrier_freq_list_utra_fdd.as_ref() { + for carrier_info in &carrier_info_list.0 { + if let Some(CellReselectionPriority(p)) = carrier_info.cell_reselection_priority { + if p == 0 { + return Some(Event { + event_type: EventType::QualitativeWarning { severity: Severity::High }, + message: "LTE cell advertised a 3G cell for priority 0 reselection".to_string(), + }); + } + } + } + } + if let Some(carrier_info_list) = sib6.carrier_freq_list_utra_tdd.as_ref() { + for carrier_info in &carrier_info_list.0 { + if let Some(CellReselectionPriority(p)) = carrier_info.cell_reselection_priority { + if p == 0 { + return Some(Event { + event_type: EventType::QualitativeWarning { severity: Severity::High }, + message: "LTE cell advertised a 3G cell for priority 0 reselection".to_string(), + }); + } + } + } + } + }, + SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib7(SystemInformationBlockType7 { carrier_freqs_info_list: Some(carrier_info_list), .. }) => { + for carrier_info in &carrier_info_list.0 { + if let Some(CellReselectionPriority(p)) = carrier_info.common_info.cell_reselection_priority { + if p == 0 { + return Some(Event { + event_type: EventType::QualitativeWarning { severity: Severity::High }, + message: "LTE cell advertised a 2G cell for priority 0 reselection".to_string(), + }); + } + } + } + }, + _ => {}, + } + } + None + } +} diff --git a/lib/src/analysis/util.rs b/lib/src/analysis/util.rs index a011dfb..c021ca8 100644 --- a/lib/src/analysis/util.rs +++ b/lib/src/analysis/util.rs @@ -1,7 +1,34 @@ + +/// Unpacks a pattern, or returns None. +/// +/// # Examples +/// Suppose you've got some highly nested enum: +/// ``` +/// enum Foo { +/// A(Bar), +/// B, +/// } +/// +/// enum Baz { +/// C(Bang) +/// } +/// +/// struct Bang; +/// ``` +/// +/// You can use `unpack!` to unroll it like this: +/// ``` +/// fn get_bang(foo: Foo) -> Option { +/// unpack!(Foo::A(bar) = foo); +/// unpack!(Baz::C(bang) = bar); +/// bang +/// } +/// ``` macro_rules! unpack { ($pat:pat = $val:expr) => { let $pat = $val else { return None; }; }; } +// this is apparently how you make a macro publicly usable from this module pub(crate) use unpack;