Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ echo out_dir=%out_dir%

REM Set the path to the eBPF-for-Windows binaries and include files,
REM We build ARM64 binaries on x64 machine, so we need to set the path to the x64 binaries
SET eBPF_for_Windows_bin_path=%root_path%packages\eBPF-for-Windows.x64.1.0.0-rc1\build\native\bin
SET eBPF_for_Windows_inc_path=%root_path%packages\eBPF-for-Windows.%eBPF_Platform%.1.0.0-rc1\build\native\include
SET eBPF_for_Windows_bin_path=%root_path%packages\eBPF-for-Windows.x64.1.5.0\build\native\bin
SET eBPF_for_Windows_inc_path=%root_path%packages\eBPF-for-Windows.%eBPF_Platform%.1.5.0\build\native\include
SET bin_skim_path=%root_path%packages\Microsoft.CodeAnalysis.BinSkim.1.9.5\tools\netcoreapp3.1\win-x64

if "%CleanBuild%"=="clean" (
Expand Down
63 changes: 50 additions & 13 deletions ebpf/redirect.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ struct bpf_map_def policy_map = {
.value_size = sizeof(destination_entry_t),
.max_entries = 10};

#pragma clang section data = "maps"
struct bpf_map_def config_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(uint32_t),
.value_size = sizeof(struct gpa_config_entry),
.max_entries = 1};

#pragma clang section data = "maps"
struct bpf_map_def skip_process_map = {
.type = BPF_MAP_TYPE_HASH,
Expand All @@ -27,6 +34,13 @@ struct bpf_map_def audit_map = {
.value_size = sizeof(sock_addr_audit_entry_t),
.max_entries = 1000};

#pragma clang section data = "maps"
struct bpf_map_def audit_only_map = {
.type = BPF_MAP_TYPE_RINGBUF,
.key_size = 0,
.value_size = 0,
.max_entries = 256 * 1024};

/*
check the current pid in the skip_process map.
return 1 if found, otherwise return 0.
Expand All @@ -42,13 +56,21 @@ check_skip_process_map_entry(uint32_t pid)
return (skip_entry != NULL) ? 1 : 0;
}

inline __attribute__((always_inline)) int
local_ip_bind_monitor_only_enabled(void)
{
uint32_t key = GPA_CONFIG_LOCAL_IP_BIND_MONITOR_ONLY;
struct gpa_config_entry *entry = bpf_map_lookup_elem(&config_map, &key);
return entry != NULL && entry->enabled != 0;
}

/*
update audit map entry if not skip redirecting.
return 0 if the entry is updated, otherwise
return 1 if pid found in the skip_process_map.
*/
inline __attribute__((always_inline)) int
update_audit_map_entry(bpf_sock_addr_t *ctx, uint32_t destination_ipv4, uint32_t address_family)
update_audit_map_entry(bpf_sock_addr_t *ctx, int audit_only, uint32_t destination_ipv4, uint32_t address_family)
{
uint64_t pid_tip = bpf_get_current_pid_tgid();
uint32_t pid = (uint32_t)(pid_tip >> 32);
Expand Down Expand Up @@ -79,6 +101,20 @@ update_audit_map_entry(bpf_sock_addr_t *ctx, uint32_t destination_ipv4, uint32_t
entry.destination_port = ctx->user_port;
entry.address_family = address_family;
uint16_t source_port = ctx->msg_src_port;
if (audit_only)
{
struct gpa_audit_only_event event = {0};
event.kernel_timestamp_ns = bpf_ktime_get_ns();
event.local_ipv4 = ctx->msg_src_ip4;
event.audit = entry;
uint64_t ret = bpf_ringbuf_output(&audit_only_map, &event, sizeof(event), 0);
if (ret != 0)
{
bpf_printk("Failed to emit audit-only event with results: %u.", ret);
}
return 0;
}

if (source_port == 0)
{
int32_t result = bpf_sock_addr_set_redirect_context(ctx, &entry, sizeof(sock_addr_audit_entry_t));
Expand Down Expand Up @@ -123,23 +159,22 @@ authorize_v4(bpf_sock_addr_t *ctx)
{
bpf_printk("Found v4 proxy entry value: %u, %u", policy->destination_ip.ipv4, policy->destination_port);

uint32_t source_ip = ctx->msg_src_ip4;
int audit_only = local_ip_bind_monitor_only_enabled() && // check the config map for localIPBindMonitorOnly
source_ip != 0 && (source_ip & 0xff) != 0x7f; // check if the source ip is set and not loopback

// update to the audit map before changing the destination ip and port.
if (update_audit_map_entry(ctx, ctx->user_ip4, GPA_ADDRESS_FAMILY_IPV4) == 1)
if (update_audit_map_entry(ctx, audit_only, ctx->user_ip4, GPA_ADDRESS_FAMILY_IPV4) == 1)
{
bpf_printk("Found skip process entry, skip the redirection.");
return BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT;
}

// if (ctx->msg_src_ip4 == 0)
// {
// bpf_printk("Local/source ip is not set, redirect to loopback ip.");
// ctx->user_ip4 = policy->destination_ip.ipv4;
// }
// else
// {
// ctx->user_ip4 = ctx->msg_src_ip4;
// bpf_printk("Local/source ip is set, redirect to source ip:%u.", ctx->user_ip4);
// }
if (audit_only)
{
bpf_printk("Source address is explicitly bound, audit without redirecting.");
return BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT;
}

bpf_printk("redirecting to destination loopback ip.");
ctx->user_ip4 = policy->destination_ip.ipv4;
Expand Down Expand Up @@ -196,7 +231,9 @@ int authorize_connect6(bpf_sock_addr_t *ctx)
if (policy != NULL)
{
bpf_printk("Found IPv4-mapped proxy entry.");
if (update_audit_map_entry(ctx, destination_ipv4, GPA_ADDRESS_FAMILY_IPV6) == 1)
//TODO: check bind to IPv4 mapped address, if so, skip the redirection and update the audit map.
int audit_only = 0;
if (update_audit_map_entry(ctx, audit_only, destination_ipv4, GPA_ADDRESS_FAMILY_IPV6) == 1)
{
bpf_printk("Found skip process entry, skip the redirection.");
return BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT;
Expand Down
85 changes: 67 additions & 18 deletions linux-ebpf/ebpf_cgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ struct
__uint(max_entries, 10);
} policy_map SEC(".maps");

struct
{
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32);
__type(value, struct gpa_config_entry);
__uint(max_entries, 1);
} config_map SEC(".maps");

struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
Expand All @@ -38,6 +46,12 @@ struct
__uint(max_entries, 200); // LRU evicts oldest on overflow
} audit_map SEC(".maps");

struct
{
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} audit_only_map SEC(".maps");

struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
Expand All @@ -61,13 +75,21 @@ check_skip_process_map_entry(__u32 pid)
return (skip_entry != NULL) ? 1 : 0;
}

static __always_inline int
local_ip_bind_monitor_only_enabled(void)
{
__u32 key = GPA_CONFIG_LOCAL_IP_BIND_MONITOR_ONLY;
struct gpa_config_entry *entry = bpf_map_lookup_elem(&config_map, &key);
return entry != NULL && entry->enabled != 0;
}

/*
update audit map entry if not skip redirecting.
return 0 if the entry is updated, otherwise
return 1 if pid found in the skip_process_map.
*/
static __always_inline int
update_local_map_entry(struct bpf_sock_addr *ctx, __be32 destination_ipv4, __u32 address_family)
update_local_map_entry(struct bpf_sock_addr *ctx, __u32 audit_only, __be32 destination_ipv4, __u32 address_family)
{
__u64 pid_tip = bpf_get_current_pid_tgid();
__u32 pid = (__u32)(pid_tip >> 32);
Expand All @@ -85,6 +107,7 @@ update_local_map_entry(struct bpf_sock_addr *ctx, __be32 destination_ipv4, __u32
entry.destination_ipv4 = destination_ipv4;
entry.destination_port = ctx->user_port;
entry.protocol = ctx->protocol;
entry.audit_only = audit_only;
entry.address_family = address_family;

__u64 ret = bpf_map_update_elem(&local_map, &pid_tip, &entry, 0);
Expand Down Expand Up @@ -114,27 +137,30 @@ authorize_v4(struct bpf_sock_addr *ctx)
{
bpf_printk("authorize_v4: Found v4 proxy entry value: %u, %u", policy->destination_ip.ipv4, policy->destination_port);

// At connect4, msg_src_ip4 is not valid; it is only populated for
// UDP sendmsg hooks. A concrete address set by bind(2) is available
// from the socket before TCP performs automatic source selection.
__u32 source_ip = ctx->sk != NULL ? ctx->sk->src_ip4 : 0;
__u32 source_ip_host = bpf_ntohl(source_ip);
__u32 audit_only = local_ip_bind_monitor_only_enabled() &&
source_ip != 0 &&
(source_ip_host & 0xff000000) != 0x7f000000;

// update to the audit map before changing the destination ip and port.
if (update_local_map_entry(ctx, ctx->user_ip4, GPA_ADDRESS_FAMILY_IPV4) == 1)
if (update_local_map_entry(ctx, audit_only, ctx->user_ip4, GPA_ADDRESS_FAMILY_IPV4) == 1)
{
bpf_printk("authorize_v4: Found skip process entry, skip the redirection.");
return BPF_SOCK_ADDR_VERDICT_PROCEED;
}

// TODO: check if the local ip is set.
// __u32 local_ip;
// __u64 read = bpf_probe_read_kernel(&local_ip, sizeof(__u32), &ctx->msg_src_ip4);
// if (read == 0 && local_ip != 0)
// {
// // read the local ip from the msg_src_ip4 successfully and ip is set.
// ctx->user_ip4 = local_ip;
// bpf_printk("authorize_v4: Local/source ip is set, redirect to source ip:%u.", local_ip);
// }
// else
if (audit_only)
{
ctx->user_ip4 = policy->destination_ip.ipv4;
bpf_printk("authorize_v4: Local/source ip is not set, redirect to loopback ip.");
bpf_printk("authorize_v4: Source address is explicitly bound, audit without redirecting.");
return BPF_SOCK_ADDR_VERDICT_PROCEED;
}

ctx->user_ip4 = policy->destination_ip.ipv4;
bpf_printk("authorize_v4: Local/source ip is not set, redirect to loopback ip.");
ctx->user_port = policy->destination_port;
}

Expand Down Expand Up @@ -184,7 +210,9 @@ int connect6(struct bpf_sock_addr *ctx)
if (policy != NULL)
{
bpf_printk("connect6: Found IPv4-mapped proxy entry.");
if (update_local_map_entry(ctx, destination_ipv4, GPA_ADDRESS_FAMILY_IPV6) == 1)
// TODO: check bind to IPv4 mapped address, if so, skip the redirection and update the audit map.
__u32 audit_only = 0;
if (update_local_map_entry(ctx, audit_only, destination_ipv4, GPA_ADDRESS_FAMILY_IPV6) == 1)
{
bpf_printk("connect6: Found skip process entry, skip the redirection.");
return BPF_SOCK_ADDR_VERDICT_PROCEED;
Expand All @@ -202,7 +230,7 @@ int connect6(struct bpf_sock_addr *ctx)
}

static __always_inline int
update_audit_map_entry_sk(__u32 local_port, struct gpa_sock_addr_local_entry *local_entry)
update_audit_map_entry_sk(__u32 local_port, __u32 local_ipv4, struct gpa_sock_addr_local_entry *local_entry)
{
struct gpa_audit_key key = {0};
key.protocol = local_entry->protocol;
Expand All @@ -216,7 +244,19 @@ update_audit_map_entry_sk(__u32 local_port, struct gpa_sock_addr_local_entry *lo
entry.destination_port = local_entry->destination_port;
entry.address_family = local_entry->address_family;

__u64 ret = bpf_map_update_elem(&audit_map, &key, &entry, 0);
__u64 ret;
if (local_entry->audit_only)
{
struct gpa_audit_only_event event = {0};
event.kernel_timestamp_ns = bpf_ktime_get_ns();
event.local_ipv4 = local_ipv4;
event.audit = entry;
ret = bpf_ringbuf_output(&audit_only_map, &event, sizeof(event), 0);
}
else
{
ret = bpf_map_update_elem(&audit_map, &key, &entry, 0);
}
if (ret != 0)
{
bpf_printk("update_audit_map_entry_sk: Failed to update audit map entry with results:%u.", ret);
Expand All @@ -239,6 +279,15 @@ trace_tcp_connect(struct sock *sk)
// local scalars (no preserve_access_index), so their offsets are NOT
// relocated - this is required, otherwise the verifier rejects writes that
// would land outside our local stack copy.
__u16 skc_family = BPF_CORE_READ(sk, __sk_common.skc_family);
if (skc_family != AF_INET)
{
// Only support IPv4.
return 0;
}
__be32 skc_daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);
__be32 skc_rcv_saddr = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
__be16 skc_dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
__u16 skc_num = BPF_CORE_READ(sk, __sk_common.skc_num);

__u64 pid_tgid = bpf_get_current_pid_tgid();
Expand All @@ -253,7 +302,7 @@ trace_tcp_connect(struct sock *sk)
struct gpa_sock_addr_local_entry *local_entry = bpf_map_lookup_elem(&local_map, &pid_tgid);
if (local_entry != NULL)
{
update_audit_map_entry_sk(skc_num, local_entry);
update_audit_map_entry_sk(skc_num, skc_rcv_saddr, local_entry);
__u64 ret = bpf_map_delete_elem(&local_map, &pid_tgid);
if (ret != 0)
{
Expand Down
4 changes: 2 additions & 2 deletions packages.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="eBPF-for-Windows.x64" version="1.0.0-rc1" />
<package id="eBPF-for-Windows.arm64" version="1.0.0-rc1" />
<package id="eBPF-for-Windows.x64" version="1.5.0" />
<package id="eBPF-for-Windows.arm64" version="1.5.0" />
<package id="VC2012Redist" version="14.29.30133" />
<package id="Microsoft.CodeAnalysis.BinSkim" version="1.9.5" />
</packages>
1 change: 1 addition & 0 deletions proxy_agent/config/GuestProxyAgent.linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"fileLogLevel": "Trace",
"fileLogLevelForEvents": "Info",
"fileLogLevelForSystemEvents": "Info",
"localIPBindMonitorOnly": true,
"canonicalRequestMode": "Shadow"
}
1 change: 1 addition & 0 deletions proxy_agent/config/GuestProxyAgent.windows.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"fileLogLevel": "Trace",
"fileLogLevelForEvents": "Info",
"fileLogLevelForSystemEvents": "Info",
"localIPBindMonitorOnly": true,
"canonicalRequestMode": "Shadow"
}
18 changes: 17 additions & 1 deletion proxy_agent/src/common/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ pub fn get_enable_http_proxy_trace() -> bool {
SYSTEM_CONFIG.enableHttpProxyTrace.unwrap_or(false)
}

pub fn get_local_ip_bind_monitor_only() -> bool {
SYSTEM_CONFIG.get_local_ip_bind_monitor_only()
}

/// Rollout flag for the Innovation 2.1 canonical request pipeline.
///
/// Read from the optional `canonicalRequestMode` key in the GPA config
Expand Down Expand Up @@ -115,6 +119,8 @@ pub struct Config {
/// This is an optional config, mainly for manual debugging purpose
#[serde(skip_serializing_if = "Option::is_none")]
enableHttpProxyTrace: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
localIPBindMonitorOnly: Option<bool>,
/// Innovation 2.1 canonical request rollout flag.
/// Optional; absent or unparseable values resolve to
/// [`crate::proxy::canonical::CanonicalMode::Off`] so production
Expand Down Expand Up @@ -230,6 +236,10 @@ impl Config {
None
}

pub fn get_local_ip_bind_monitor_only(&self) -> bool {
self.localIPBindMonitorOnly.unwrap_or(false)
}

/// Resolve the canonical-request rollout flag.
///
/// Returns [`crate::proxy::canonical::CanonicalMode::Off`] when the
Expand Down Expand Up @@ -278,7 +288,7 @@ mod tests {
Err(err) => panic!("Failed to create folder: {}", err),
}
let config_file_path = temp_test_path.join("test_config.json");
let config = create_config_file(config_file_path);
let mut config = create_config_file(config_file_path);

assert_eq!(
r#"C:\logFolderName"#,
Expand Down Expand Up @@ -331,6 +341,10 @@ mod tests {
);
}

assert!(config.get_local_ip_bind_monitor_only());
config.localIPBindMonitorOnly = None;
assert!(!config.get_local_ip_bind_monitor_only());

assert_eq!(
proxy_agent_shared::logger::LoggerLevel::Info,
config.get_file_log_level_for_events().unwrap(),
Expand Down Expand Up @@ -364,6 +378,7 @@ mod tests {
"hostGAPluginSupport": 1,
"imdsSupport": 1,
"ebpfProgramName": "ebpfProgramName",
"localIPBindMonitorOnly": true,
"fileLogLevelForEvents": "Info",
"fileLogLevelForSystemEvents": "Info"
}"#
Expand All @@ -378,6 +393,7 @@ mod tests {
"hostGAPluginSupport": 1,
"imdsSupport": 1,
"ebpfProgramName": "ebpfProgramName",
"localIPBindMonitorOnly": true,
"fileLogLevelForEvents": "Info",
"fileLogLevelForSystemEvents": "Info"
}"#
Expand Down
Loading
Loading