diff --git a/windows_kext/PacketFlow.md b/windows_kext/PacketFlow.md index 67eab5f3..d4adecbc 100644 --- a/windows_kext/PacketFlow.md +++ b/windows_kext/PacketFlow.md @@ -26,12 +26,11 @@ For outgoing connections this logic fallows: - If Packet is not TCP/UDP forward to packet layer For incoming connection this logic fallow: - - Packet enter in one of the Packet layer, if packet is TCP or UDP it will be forwarded to ALE layer. From there: + - Packet enter in one of the Packet layer: 1. Save packet and absorb. 2. Send an event to Portmaster. - 2. Create a cache entry. + 2. Create a cache entry if the protocol is TCP or UDP. 3. Wait for Portmasters decision. - - If Packet is not TCP/UDP. It will be handled only by the packet layer. If more packets arrive before Portmaster returns a decision, packet will be absorbed and another event will be sent. @@ -49,7 +48,9 @@ The next steps depend of the direction of the packet and the verdict - Always Allow - this connections are solely handled by the packet layer. (This is true only for outgoing connections) * Permanent or Temporary Verdict / Incoming connection - - Allow / Block / Drop directly in the ALE layer. They always go through the packet layer first no need to do anything special + - Allow / Block / Drop. Handled by the Packet layer + +> There is no defined ALE layers for inbound connection. Inbound packets are handed compactly by the packet layer Fallowing specifics apply to the ALE layer: 1. Connections with flag `reauthorize == false` are special. When the flag is `false` that means that a applications is calling a function `connect()` or `accept()` for a connection. This is a special case because we control the result of the function, telling the application that it's allowed or not allowed to continue with the connection. Since we are making request to Portmaster we need to take longer time. This is done with pending the packet. This allows the kernel extension to pause the event and continue when it has the verdict. See `ale_callouts.rs -> save_packet()` function. diff --git a/windows_kext/driver/src/ale_callouts.rs b/windows_kext/driver/src/ale_callouts.rs index c45b7f4c..7ab824e5 100644 --- a/windows_kext/driver/src/ale_callouts.rs +++ b/windows_kext/driver/src/ale_callouts.rs @@ -7,10 +7,7 @@ use smoltcp::wire::{ IpAddress, IpProtocol, Ipv4Address, Ipv6Address, IPV4_HEADER_LEN, IPV6_HEADER_LEN, }; use wdk::filter_engine::callout_data::CalloutData; -use wdk::filter_engine::layer::{ - self, FieldsAleAuthConnectV4, FieldsAleAuthConnectV6, FieldsAleAuthRecvAcceptV4, - FieldsAleAuthRecvAcceptV6, ValueType, -}; +use wdk::filter_engine::layer::{self, FieldsAleAuthConnectV4, FieldsAleAuthConnectV6, ValueType}; use wdk::filter_engine::net_buffer::NetBufferList; use wdk::filter_engine::packet::{Injector, TransportPacketList}; @@ -87,24 +84,6 @@ pub fn ale_layer_connect_v4(data: CalloutData) { ale_layer_auth(data, ale_data); } -pub fn ale_layer_accept_v4(data: CalloutData) { - type Fields = FieldsAleAuthRecvAcceptV4; - let ale_data = AleLayerData { - is_ipv6: false, - reauthorize: data.is_reauthorize(Fields::Flags as usize), - process_id: data.get_process_id().unwrap_or(0), - protocol: get_protocol(&data, Fields::IpProtocol as usize), - direction: Direction::Inbound, - local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), - sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), - }; - ale_layer_auth(data, ale_data); -} - pub fn ale_layer_connect_v6(data: CalloutData) { type Fields = FieldsAleAuthConnectV6; @@ -125,24 +104,6 @@ pub fn ale_layer_connect_v6(data: CalloutData) { ale_layer_auth(data, ale_data); } -pub fn ale_layer_accept_v6(data: CalloutData) { - type Fields = FieldsAleAuthRecvAcceptV6; - let ale_data = AleLayerData { - is_ipv6: true, - reauthorize: data.is_reauthorize(Fields::Flags as usize), - process_id: data.get_process_id().unwrap_or(0), - protocol: get_protocol(&data, Fields::IpProtocol as usize), - direction: Direction::Inbound, - local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), - sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), - }; - ale_layer_auth(data, ale_data); -} - fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) { let Some(device) = crate::entry::get_device() else { return; @@ -265,7 +226,7 @@ fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) { }; // Connection is not in cache, add it. - crate::dbg!("adding connection: {} PID: {}", key, ale_data.process_id); + crate::dbg!("ale layer adding connection: {} PID: {}", key, ale_data.process_id); if ale_data.is_ipv6 { let conn = ConnectionV6::from_key(&key, ale_data.process_id, ale_data.direction).unwrap(); @@ -289,15 +250,12 @@ fn save_packet( ) -> Result { let mut packet_list = None; let mut save_packet_list = true; - match ale_data.protocol { - IpProtocol::Tcp => { - if let Direction::Outbound = ale_data.direction { - // Only time a packet data is missing is during connect state of outbound TCP connection. - // Don't save packet list only if connection is outbound, reauthorize is false and the protocol is TCP. - save_packet_list = ale_data.reauthorize; - } + if ale_data.protocol == IpProtocol::Tcp { + if let Direction::Outbound = ale_data.direction { + // Only time a packet data is missing is during connect state of outbound TCP connection. + // Don't save packet list only if connection is outbound, reauthorize is false and the protocol is TCP. + save_packet_list = ale_data.reauthorize; } - _ => {} }; if save_packet_list { packet_list = create_packet_list(device, callout_data, ale_data); diff --git a/windows_kext/driver/src/callouts.rs b/windows_kext/driver/src/callouts.rs index 71701692..0999a707 100644 --- a/windows_kext/driver/src/callouts.rs +++ b/windows_kext/driver/src/callouts.rs @@ -20,15 +20,6 @@ pub fn get_callout_vec() -> Vec { FilterType::Resettable, ale_callouts::ale_layer_connect_v4, ), - Callout::new( - "AleLayerInboundV4", - "ALE layer for inbound connections for ipv4", - 0xc6021395_0724_4e2c_ae20_3dde51fc3c68, - Layer::AleAuthRecvAcceptV4, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::Resettable, - ale_callouts::ale_layer_accept_v4, - ), Callout::new( "AleLayerOutboundV6", "ALE layer for outbound connections for ipv6", @@ -38,15 +29,6 @@ pub fn get_callout_vec() -> Vec { FilterType::Resettable, ale_callouts::ale_layer_connect_v6, ), - Callout::new( - "AleLayerInboundV6", - "ALE layer for inbound connections for ipv6", - 0xd24480da_38fa_4099_9383_b5c83b69e4f2, - Layer::AleAuthRecvAcceptV6, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::Resettable, - ale_callouts::ale_layer_accept_v6, - ), // ----------------------------------------- // ALE connection end layers Callout::new( diff --git a/windows_kext/driver/src/connection_cache.rs b/windows_kext/driver/src/connection_cache.rs index 6e149508..665e60f4 100644 --- a/windows_kext/driver/src/connection_cache.rs +++ b/windows_kext/driver/src/connection_cache.rs @@ -59,7 +59,7 @@ impl ConnectionCache { process_connection: fn(&ConnectionV4) -> Option, ) -> Option { let _guard = self.lock_v4.read_lock(); - self.connections_v4.read(&key, process_connection) + self.connections_v4.read(key, process_connection) } pub fn read_connection_v6( @@ -68,7 +68,7 @@ impl ConnectionCache { process_connection: fn(&ConnectionV6) -> Option, ) -> Option { let _guard = self.lock_v6.read_lock(); - self.connections_v6.read(&key, process_connection) + self.connections_v6.read(key, process_connection) } pub fn end_connection_v4(&mut self, key: Key) -> Option { diff --git a/windows_kext/driver/src/connection_map.rs b/windows_kext/driver/src/connection_map.rs index 282da153..bf2210f8 100644 --- a/windows_kext/driver/src/connection_map.rs +++ b/windows_kext/driver/src/connection_map.rs @@ -111,7 +111,7 @@ impl ConnectionMap { pub fn end(&mut self, key: Key) -> Option { if let Some(connections) = self.0.get_mut(&key.small()) { - for (_, conn) in connections.iter_mut().enumerate() { + for conn in connections.iter_mut() { if conn.remote_equals(&key) { conn.end(wdk::utils::get_system_timestamp_ms()); return Some(conn.clone()); @@ -124,7 +124,7 @@ impl ConnectionMap { pub fn end_all_on_port(&mut self, key: (IpProtocol, u16)) -> Option> { if let Some(connections) = self.0.get_mut(&key) { let mut vec = Vec::with_capacity(connections.len()); - for (_, conn) in connections.iter_mut().enumerate() { + for conn in connections.iter_mut() { if !conn.has_ended() { conn.end(wdk::utils::get_system_timestamp_ms()); vec.push(conn.clone()); diff --git a/windows_kext/driver/src/device.rs b/windows_kext/driver/src/device.rs index e5eeca7d..b7175207 100644 --- a/windows_kext/driver/src/device.rs +++ b/windows_kext/driver/src/device.rs @@ -46,9 +46,7 @@ impl Device { Err(err) => return Err(alloc::format!("filter engine error: {}", err)), }; - if let Err(err) = filter_engine.commit(callouts::get_callout_vec()) { - return Err(err); - } + filter_engine.commit(callouts::get_callout_vec())?; Ok(Self { filter_engine, diff --git a/windows_kext/driver/src/id_cache.rs b/windows_kext/driver/src/id_cache.rs index e8474538..9a0c4b27 100644 --- a/windows_kext/driver/src/id_cache.rs +++ b/windows_kext/driver/src/id_cache.rs @@ -56,7 +56,7 @@ impl IdCache { } } -fn get_payload<'a>(packet: &'a Packet) -> Option<&'a [u8]> { +fn get_payload(packet: &Packet) -> Option<&[u8]> { match packet { Packet::PacketLayer(nbl, _) => nbl.get_data(), Packet::AleLayer(defer) => { @@ -65,7 +65,7 @@ fn get_payload<'a>(packet: &'a Packet) -> Option<&'a [u8]> { wdk::filter_engine::callout_data::ClassifyDefer::Reauthorization(_, p) => p, }; if let Some(tpl) = p { - tpl.net_buffer_list_queue.get_data() + tpl.net_buffer_list.get_data() } else { None } diff --git a/windows_kext/driver/src/packet_callouts.rs b/windows_kext/driver/src/packet_callouts.rs index 9971d6af..6b0cf8e4 100644 --- a/windows_kext/driver/src/packet_callouts.rs +++ b/windows_kext/driver/src/packet_callouts.rs @@ -13,7 +13,6 @@ use crate::connection_cache::ConnectionCache; use crate::connection_map::Key; use crate::device::{Device, Packet}; use crate::packet_util::{get_key_from_nbl_v4, get_key_from_nbl_v6, Redirect}; -use crate::{err, warn}; // IP packet layers pub fn ip_packet_layer_outbound_v4(data: CalloutData) { @@ -141,7 +140,7 @@ fn ip_packet_layer( } { Ok(key) => key, Err(err) => { - warn!("failed to get key from nbl: {}", err); + crate::warn!("failed to get key from nbl: {}", err); return; } }; @@ -151,7 +150,7 @@ fn ip_packet_layer( return; } - let mut is_tmp_verdict = false; + let mut send_request_to_portmaster = true; let mut process_id = 0; if matches!( @@ -164,13 +163,17 @@ fn ip_packet_layer( process_id = conn_info.process_id; // Check if there is action for this connection. match conn_info.verdict { - Verdict::Undecided | Verdict::Accept | Verdict::Block | Verdict::Drop => { - is_tmp_verdict = true + Verdict::Undecided | Verdict::Accept | Verdict::Block | Verdict::Drop => {} + Verdict::PermanentAccept => { + send_request_to_portmaster = false; + data.action_permit(); + } + Verdict::PermanentBlock => { + send_request_to_portmaster = false; + data.action_block(); } - Verdict::PermanentAccept => data.action_permit(), - Verdict::PermanentBlock => data.action_block(), Verdict::Undeterminable | Verdict::PermanentDrop | Verdict::Failed => { - data.block_and_absorb() + data.block_and_absorb(); } Verdict::RedirectNameServer | Verdict::RedirectTunnel => { if let Some(redirect_info) = conn_info.redirect_info.take() { @@ -186,10 +189,10 @@ fn ip_packet_layer( Ok(mut packet) => { let _ = packet.redirect(redirect_info); if let Err(err) = device.inject_packet(packet, false) { - err!("failed to inject packet: {}", err); + crate::err!("failed to inject packet: {}", err); } } - Err(err) => err!("failed to clone packet: {}", err), + Err(err) => crate::err!("failed to clone packet: {}", err), } } @@ -199,24 +202,20 @@ fn ip_packet_layer( } } } else { - // TCP and UDP always need to go through ALE layer first. - if matches!(direction, Direction::Inbound) { - // If it's an inbound packet and the connection is not found, we need to continue to ALE layer - data.action_permit(); - return; + // Connections is not in the cache. + crate::dbg!("packet layer adding connection: {} PID: 0", key); + if ipv6 { + let conn = ConnectionV6::from_key(&key, 0, direction).unwrap(); + device.connection_cache.add_connection_v6(conn); } else { - // This happens sometimes. Leave the decision for portmaster. TODO(vladimir): Find out why. - err!("Invalid state for: {}", key); - is_tmp_verdict = true; + let conn = ConnectionV4::from_key(&key, 0, direction).unwrap(); + device.connection_cache.add_connection_v4(conn); } } - } else { - // Every other protocol treat as a tmp verdict. - is_tmp_verdict = true; } - // Clone packet and send to Portmaster if it's a temporary verdict. - if is_tmp_verdict { + // Clone packet and send to Portmaster. + if send_request_to_portmaster { let packet = match clone_packet( device, nbl, @@ -228,7 +227,7 @@ fn ip_packet_layer( ) { Ok(p) => p, Err(err) => { - err!("failed to clone packet: {}", err); + crate::err!("failed to clone packet: {}", err); return; } }; @@ -236,6 +235,7 @@ fn ip_packet_layer( let info = device .packet_cache .push((key, packet), process_id, direction, false); + // Send to Portmaster if let Some(info) = info { let _ = device.event_queue.push(info); @@ -278,7 +278,7 @@ fn get_connection_info( ) -> Option { if ipv6 { let conn_info = connection_cache.read_connection_v6( - &key, + key, |conn: &ConnectionV6| -> Option { // Function is is behind spin lock. Just copy and return. Some(ConnectionInfo::from_connection(conn)) @@ -287,7 +287,7 @@ fn get_connection_info( return conn_info; } else { let conn_info = connection_cache.read_connection_v4( - &key, + key, |conn: &ConnectionV4| -> Option { // Function is is behind spin lock. Just copy and return. Some(ConnectionInfo::from_connection(conn)) diff --git a/windows_kext/wdk/src/filter_engine/packet.rs b/windows_kext/wdk/src/filter_engine/packet.rs index f6b9018f..85e26006 100644 --- a/windows_kext/wdk/src/filter_engine/packet.rs +++ b/windows_kext/wdk/src/filter_engine/packet.rs @@ -24,7 +24,7 @@ use super::{callout_data::CalloutData, net_buffer::NetBufferList}; pub struct TransportPacketList { ipv6: bool, - pub net_buffer_list_queue: NetBufferList, + pub net_buffer_list: NetBufferList, remote_ip: [u8; 16], endpoint_handle: u64, remote_scope_id: SCOPE_ID, @@ -112,7 +112,7 @@ impl Injector { TransportPacketList { ipv6, - net_buffer_list_queue: net_buffer_list, + net_buffer_list, remote_ip, endpoint_handle: callout_data.get_transport_endpoint_handle().unwrap_or(0), remote_scope_id: callout_data @@ -153,7 +153,7 @@ impl Injector { }; let address_family = if packet_list.ipv6 { AF_INET6 } else { AF_INET }; - let net_buffer_list = packet_list.net_buffer_list_queue; + let net_buffer_list = packet_list.net_buffer_list; // Escape the stack. Packet buffer should be valid until the packet is injected. let boxed_nbl = Box::new(net_buffer_list); let raw_nbl = boxed_nbl.nbl; @@ -338,6 +338,8 @@ unsafe extern "C" fn free_packet( if let Some(nbl) = net_buffer_list.as_ref() { if let Err(err) = check_ntstatus(nbl.Status) { crate::err!("inject status: {}", err); + } else { + crate::dbg!("inject status: Ok"); } } _ = Box::from_raw(context as *mut NetBufferList);