Files
portmaster/windows_kext/driver/src/ale_callouts.rs
Vladimir Stoilov fe070b4f56 Feature/kext default action drop (#1747)
* [windows_kext] Make default action to drop

* [windows_kext] Minor improvments
2024-11-25 14:03:35 +02:00

512 lines
20 KiB
Rust

use crate::connection::{Connection, ConnectionV4, ConnectionV6, Direction, Verdict};
use crate::connection_map::Key;
use crate::device::{Device, Packet};
use crate::info;
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, ValueType};
use wdk::filter_engine::net_buffer::NetBufferList;
use wdk::filter_engine::packet::{Injector, TransportPacketList};
// ALE Layers
#[derive(Debug)]
#[allow(dead_code)]
struct AleLayerData {
is_ipv6: bool,
reauthorize: bool,
process_id: u64,
protocol: IpProtocol,
direction: Direction,
local_ip: IpAddress,
local_port: u16,
remote_ip: IpAddress,
remote_port: u16,
interface_index: u32,
sub_interface_index: u32,
}
impl AleLayerData {
fn as_key(&self) -> Key {
let mut local_port = 0;
let mut remote_port = 0;
match self.protocol {
IpProtocol::Tcp | IpProtocol::Udp => {
local_port = self.local_port;
remote_port = self.remote_port;
}
_ => {}
}
Key {
protocol: self.protocol,
local_address: self.local_ip,
local_port,
remote_address: self.remote_ip,
remote_port,
}
}
}
fn get_protocol(data: &CalloutData, index: usize) -> IpProtocol {
IpProtocol::from(data.get_value_u8(index))
}
fn get_ipv4_address(data: &CalloutData, index: usize) -> IpAddress {
IpAddress::Ipv4(Ipv4Address::from_bytes(
&data.get_value_u32(index).to_be_bytes(),
))
}
fn get_ipv6_address(data: &CalloutData, index: usize) -> IpAddress {
IpAddress::Ipv6(Ipv6Address::from_bytes(data.get_value_byte_array16(index)))
}
pub fn ale_layer_connect_v4(data: CalloutData) {
type Fields = FieldsAleAuthConnectV4;
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::Outbound,
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: 0,
sub_interface_index: 0,
};
ale_layer_auth(data, ale_data);
}
pub fn ale_layer_connect_v6(data: CalloutData) {
type Fields = FieldsAleAuthConnectV6;
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::Outbound,
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) {
// Make the default path as drop.
data.block_and_absorb();
let Some(device) = crate::entry::get_device() else {
return;
};
// Check if packet was previously injected from the packet layer.
if device
.injector
.was_network_packet_injected_by_self(data.get_layer_data() as _, ale_data.is_ipv6)
{
data.action_permit();
return;
}
match ale_data.protocol {
IpProtocol::Tcp | IpProtocol::Udp => {
// Only TCP and UDP make sense to be supported in the ALE layer.
// Everything else is not associated with a connection and will be handled in the packet layer.
}
_ => {
// Outbound: Will be handled by packet layer next.
// Inbound: Was already handled by the packet layer.
data.action_permit();
return;
}
}
let key = ale_data.as_key();
// Check if connection is already in cache.
let verdict = if ale_data.is_ipv6 {
device
.connection_cache
.read_connection_v6(&key, |conn| -> Option<Verdict> {
// Function is behind spin lock, just copy and return.
Some(conn.verdict)
})
} else {
device
.connection_cache
.read_connection_v4(&ale_data.as_key(), |conn| -> Option<Verdict> {
// Function is behind spin lock, just copy and return.
Some(conn.verdict)
})
};
// Connection already in cache.
if let Some(verdict) = verdict {
crate::dbg!("processing existing connection: {} {}", key, verdict);
match verdict {
// No verdict yet
Verdict::Undecided => {
crate::dbg!("saving packet: {}", key);
// Connection is already pended. Save packet and wait for verdict.
match save_packet(device, &mut data, &ale_data, false) {
Ok(packet) => {
let info = device.packet_cache.push(
(key, packet),
ale_data.process_id,
ale_data.direction,
true,
);
if let Some(info) = info {
let _ = device.event_queue.push(info);
}
}
Err(err) => {
crate::err!("failed to pend packet: {}", err);
}
};
data.block_and_absorb();
}
// There is a verdict
Verdict::PermanentAccept
| Verdict::Accept
| Verdict::RedirectNameServer
| Verdict::RedirectTunnel => {
// Continue to packet layer.
data.action_permit();
}
Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => {
// Packet layer will not see this connection.
crate::dbg!("permanent block {}", key);
data.action_block();
}
Verdict::PermanentDrop => {
// Packet layer will not see this connection.
crate::dbg!("permanent drop {}", key);
data.block_and_absorb();
}
Verdict::Block => {
if let Direction::Outbound = ale_data.direction {
// Handled by packet layer.
data.action_permit();
} else {
// packet layer will still see the packets.
data.action_block();
}
}
Verdict::Drop => {
if let Direction::Outbound = ale_data.direction {
// Handled by packet layer.
data.action_permit();
} else {
// packet layer will still see the packets.
data.block_and_absorb();
}
}
}
} else {
crate::dbg!("pending connection: {} {}", key, ale_data.direction);
// Only first packet of a connection can be pended: reauthorize == false
let can_pend_connection = !ale_data.reauthorize;
match save_packet(device, &mut data, &ale_data, can_pend_connection) {
Ok(packet) => {
let info = device.packet_cache.push(
(key, packet),
ale_data.process_id,
ale_data.direction,
true,
);
if let Some(info) = info {
let _ = device.event_queue.push(info);
}
}
Err(err) => {
crate::err!("failed to pend packet: {}", err);
}
};
// Connection is not in cache, add it.
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();
device.connection_cache.add_connection_v6(conn);
} else {
let conn =
ConnectionV4::from_key(&key, ale_data.process_id, ale_data.direction).unwrap();
device.connection_cache.add_connection_v4(conn);
}
// Drop packet. It will be re-injected after Portmaster returns a verdict.
data.block_and_absorb();
}
}
fn save_packet(
device: &Device,
callout_data: &mut CalloutData,
ale_data: &AleLayerData,
pend: bool,
) -> Result<Packet, alloc::string::String> {
let mut packet_list = None;
let mut save_packet_list = true;
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);
}
if pend && matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) {
match callout_data.pend_operation(packet_list) {
Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)),
Err(err) => Err(alloc::format!("failed to defer connection: {}", err)),
}
} else {
Ok(Packet::AleLayer(callout_data.pend_filter_rest(packet_list)))
}
}
fn create_packet_list(
device: &Device,
callout_data: &mut CalloutData,
ale_data: &AleLayerData,
) -> Option<TransportPacketList> {
let mut nbl = NetBufferList::new(callout_data.get_layer_data() as _);
let mut inbound = false;
if let Direction::Inbound = ale_data.direction {
if ale_data.is_ipv6 {
nbl.retreat(IPV6_HEADER_LEN as u32, true);
} else {
nbl.retreat(IPV4_HEADER_LEN as u32, true);
}
inbound = true;
}
let address: &[u8] = match &ale_data.remote_ip {
IpAddress::Ipv4(address) => &address.0,
IpAddress::Ipv6(address) => &address.0,
};
if let Ok(clone) = nbl.clone(&device.network_allocator) {
return Some(Injector::from_ale_callout(
ale_data.is_ipv6,
callout_data,
clone,
address,
inbound,
ale_data.interface_index,
ale_data.sub_interface_index,
));
}
return None;
}
pub fn endpoint_closure_v4(data: CalloutData) {
type Fields = layer::FieldsAleEndpointClosureV4;
let Some(device) = crate::entry::get_device() else {
return;
};
let ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize);
if let ValueType::FwpUint32 = ip_address_type {
let key = Key {
protocol: get_protocol(&data, Fields::IpProtocol as usize),
local_address: get_ipv4_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_address: get_ipv4_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
};
let conn = device.connection_cache.end_connection_v4(key);
if let Some(conn) = conn {
let info = protocol::info::connection_end_event_v4_info(
data.get_process_id().unwrap_or(0),
conn.get_direction() as u8,
u8::from(get_protocol(&data, Fields::IpProtocol as usize)),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
} else {
// Invalid ip address type. Just ignore the error.
// err!(
// device.logger,
// "unknown ipv4 address type: {:?}",
// ip_address_type
// );
}
}
pub fn endpoint_closure_v6(data: CalloutData) {
type Fields = layer::FieldsAleEndpointClosureV6;
let Some(device) = crate::entry::get_device() else {
return;
};
let local_ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize);
let remote_ip_address_type = data.get_value_type(Fields::IpRemoteAddress as usize);
if let ValueType::FwpByteArray16Type = local_ip_address_type {
if let ValueType::FwpByteArray16Type = remote_ip_address_type {
let key = Key {
protocol: get_protocol(&data, Fields::IpProtocol as usize),
local_address: get_ipv6_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_address: get_ipv6_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
};
let conn = device.connection_cache.end_connection_v6(key);
if let Some(conn) = conn {
let info = protocol::info::connection_end_event_v6_info(
data.get_process_id().unwrap_or(0),
conn.get_direction() as u8,
u8::from(get_protocol(&data, Fields::IpProtocol as usize)),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
}
pub fn ale_resource_monitor(data: CalloutData) {
let Some(device) = crate::entry::get_device() else {
return;
};
match data.layer {
layer::Layer::AleResourceAssignmentV4Discard => {
type Fields = layer::FieldsAleResourceAssignmentV4;
if let Some(conns) = device.connection_cache.end_all_on_port_v4((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} Ipv4 assign request discarded pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v4_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
layer::Layer::AleResourceAssignmentV6Discard => {
type Fields = layer::FieldsAleResourceAssignmentV6;
if let Some(conns) = device.connection_cache.end_all_on_port_v6((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} Ipv6 assign request discarded pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v6_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
layer::Layer::AleResourceReleaseV4 => {
type Fields = layer::FieldsAleResourceReleaseV4;
if let Some(conns) = device.connection_cache.end_all_on_port_v4((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} released pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v4_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
layer::Layer::AleResourceReleaseV6 => {
type Fields = layer::FieldsAleResourceReleaseV6;
if let Some(conns) = device.connection_cache.end_all_on_port_v6((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} released pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v6_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
_ => {}
}
}