mirror of
https://github.com/safing/portmaster
synced 2025-04-25 13:29:10 +00:00
Merge pull request #1567 from safing/fix/kext-ale-layer-bug
[windows_kext] Fix ALE layer TCP connection pending
This commit is contained in:
commit
7270ad39ae
9 changed files with 50 additions and 109 deletions
windows_kext
PacketFlow.md
driver/src
ale_callouts.rscallouts.rsconnection_cache.rsconnection_map.rsdevice.rsid_cache.rspacket_callouts.rs
wdk/src/filter_engine
|
@ -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.
|
||||
|
|
|
@ -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<Packet, alloc::string::String> {
|
||||
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);
|
||||
|
|
|
@ -20,15 +20,6 @@ pub fn get_callout_vec() -> Vec<Callout> {
|
|||
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<Callout> {
|
|||
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(
|
||||
|
|
|
@ -59,7 +59,7 @@ impl ConnectionCache {
|
|||
process_connection: fn(&ConnectionV4) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
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<T>(
|
||||
|
@ -68,7 +68,7 @@ impl ConnectionCache {
|
|||
process_connection: fn(&ConnectionV6) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
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<ConnectionV4> {
|
||||
|
|
|
@ -111,7 +111,7 @@ impl<T: Connection + Clone> ConnectionMap<T> {
|
|||
|
||||
pub fn end(&mut self, key: Key) -> Option<T> {
|
||||
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<T: Connection + Clone> ConnectionMap<T> {
|
|||
pub fn end_all_on_port(&mut self, key: (IpProtocol, u16)) -> Option<Vec<T>> {
|
||||
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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<ConnectionInfo> {
|
||||
if ipv6 {
|
||||
let conn_info = connection_cache.read_connection_v6(
|
||||
&key,
|
||||
key,
|
||||
|conn: &ConnectionV6| -> Option<ConnectionInfo> {
|
||||
// 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<ConnectionInfo> {
|
||||
// Function is is behind spin lock. Just copy and return.
|
||||
Some(ConnectionInfo::from_connection(conn))
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue